diff --git a/CMakeLists.txt b/CMakeLists.txt index b667c78..63b508b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -249,28 +249,28 @@ write_version_header("${CMAKE_CURRENT_SOURCE_DIR}/src/" LIB_NAME "LibCIFPP") # Sources set(project_sources ${CMAKE_CURRENT_SOURCE_DIR}/src/category.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/condition.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/datablock.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/dictionary_parser.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/file.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/condition.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/datablock.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/dictionary_parser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/file.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/item.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/parser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/row.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/validate.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/validate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/text.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/utilities.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/atom_type.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/compound.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/point.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/symmetry.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/model.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/cif2pdb.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/pdb2cif.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/pdb_record.hpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/pdb2cif_remark_3.hpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/pdb2cif_remark_3.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/reconstruct.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/validate-pdbx.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/atom_type.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/compound.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/point.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/symmetry.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/model.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/cif2pdb.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/pdb2cif.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/pdb_record.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/pdb2cif_remark_3.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/pdb2cif_remark_3.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/reconstruct.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/validate-pdbx.cpp ) set(project_headers diff --git a/include/cif++/category.hpp b/include/cif++/category.hpp index 7d979ed..6db15b4 100644 --- a/include/cif++/category.hpp +++ b/include/cif++/category.hpp @@ -124,16 +124,6 @@ class multiple_results_error : public std::runtime_error } }; -// -------------------------------------------------------------------- -// These should be moved elsewhere, one day. - -/// \cond -template -inline constexpr bool is_optional_v = false; -template -inline constexpr bool is_optional_v> = true; -/// \endcond - // -------------------------------------------------------------------- /// The class category is a sequence container for rows of data values. @@ -154,8 +144,8 @@ class category friend class iterator_impl_base; using value_type = row_handle; - using reference = value_type; - using const_reference = const value_type; + using reference = row_handle; + using const_reference = const_row_handle; using iterator = iterator_impl<>; using const_iterator = const_iterator_impl<>; @@ -296,7 +286,7 @@ class category /// the category is empty. [[nodiscard]] const_reference front() const { - return { const_cast(*this), const_cast(*m_head) }; + return { *this, *m_head }; } /// @brief Return a reference to the last row in this category. @@ -312,7 +302,7 @@ class category /// the category is empty. [[nodiscard]] const_reference back() const { - return { const_cast(*this), const_cast(*m_tail) }; + return { *this, *m_tail }; } /// Return an iterator to the first row @@ -376,7 +366,7 @@ class category struct key_element_type { std::string name; ///< Name of the item - std::string value; ///< Value to be found + item_value value; ///< Value to be found bool may_be_null = false; ///< If true, value should be same or empty }; @@ -388,13 +378,10 @@ class category /// @return The row found in the index, or an undefined row_handle row_handle operator[](const key_type &key); - /// @brief Return a const row_handle for the row specified by \a key + /// @brief Return a const_row_handle for the row specified by \a key /// @param key The value for the key, items specified in the dictionary should have a value /// @return The row found in the index, or an undefined row_handle - const row_handle operator[](const key_type &key) const - { - return const_cast(this)->operator[](key); - } + const_row_handle operator[](const key_type &key) const; // -------------------------------------------------------------------- @@ -592,7 +579,7 @@ class category /// there are is not exactly one row matching @a cond /// @param cond The condition to search for /// @return Row handle to the row found - const row_handle find1(condition &&cond) const + const_row_handle find1(condition &&cond) const { return find1(cbegin(), std::move(cond)); } @@ -602,7 +589,7 @@ class category /// @param pos The position to start the search /// @param cond The condition to search for /// @return Row handle to the row found - const row_handle find1(const_iterator pos, condition &&cond) const + const_row_handle find1(const_iterator pos, condition &&cond) const { auto h = find(pos, std::move(cond)); @@ -727,7 +714,7 @@ class category /// @brief Return a const row handle to the first row that matches @a cond /// @param cond The condition to search for /// @return The const handle to the row that matches or an empty row_handle - const row_handle find_first(condition &&cond) const + const_row_handle find_first(condition &&cond) const { return find_first(cbegin(), std::move(cond)); } @@ -736,11 +723,11 @@ class category /// @param pos The location to start searching /// @param cond The condition to search for /// @return The const handle to the row that matches or an empty row_handle - const row_handle find_first(const_iterator pos, condition &&cond) const + const_row_handle find_first(const_iterator pos, condition &&cond) const { auto h = find(pos, std::move(cond)); - return h.empty() ? row_handle{} : *h.begin(); + return h.empty() ? const_row_handle{} : *h.begin(); } /// @brief Return the value for item @a item for the first row that matches condition @a cond @@ -1041,7 +1028,7 @@ class category // -------------------------------------------------------------------- - using value_provider_type = std::function; + using value_provider_type = std::function; /// \brief Update a single item named @a item_name in the rows that match /// \a cond to values provided by a callback function \a value_provider @@ -1073,7 +1060,7 @@ class category /// That means, child categories are updated if the links are absolute /// and unique. If they are not, the child category rows are split. - void update_value(condition &&cond, std::string_view item_name, std::string_view value) + void update_value(condition &&cond, std::string_view item_name, const item_value &value) { auto rs = find(std::move(cond)); std::vector rows; @@ -1087,9 +1074,9 @@ class category /// That means, child categories are updated if the links are absolute /// and unique. If they are not, the child category rows are split. - void update_value(const std::vector &rows, std::string_view item_name, std::string_view value) + void update_value(const std::vector &rows, std::string_view item_name, const item_value &value) { - update_value(rows, item_name, [value](std::string_view) + update_value(rows, item_name, [value](const item_value &) { return value; }); } @@ -1101,7 +1088,7 @@ class category /// @brief Return the name for item with index @a ix /// @param ix The index number /// @return The name of the item - [[nodiscard]] std::string_view get_item_name(uint16_t ix) const + [[nodiscard]] const std::string &get_item_name(uint16_t ix) const { if (ix >= m_items.size()) throw std::out_of_range("item index is out of range"); diff --git a/include/cif++/condition.hpp b/include/cif++/condition.hpp index 102607b..53f8e89 100644 --- a/include/cif++/condition.hpp +++ b/include/cif++/condition.hpp @@ -152,16 +152,16 @@ namespace detail virtual ~condition_impl() = default; virtual condition_impl *prepare(const category &) { return this; } - [[nodiscard]] virtual bool test(row_handle) const = 0; + [[nodiscard]] virtual bool test(const_row_handle) const = 0; virtual void str(std::ostream &) const = 0; - [[nodiscard]] virtual std::optional single() const { return {}; }; + [[nodiscard]] virtual std::optional single() const { return {}; }; virtual bool equals([[maybe_unused]] const condition_impl *rhs) const { return false; } }; struct all_condition_impl : public condition_impl { - [[nodiscard]] bool test(row_handle) const override { return true; } + [[nodiscard]] bool test(const_row_handle) const override { return true; } void str(std::ostream &os) const override { os << "*"; } }; @@ -244,7 +244,7 @@ class condition * @return true If there is a match * @return false If there is no match */ - bool operator()(row_handle r) const + bool operator()(const_row_handle r) const { assert(this->m_impl != nullptr); assert(this->m_prepared); @@ -265,12 +265,12 @@ class condition * @brief If the prepare step found out there is only one hit * this single hit can be returned by this method. * - * @return std::optional The result will contain + * @return std::optional The result will contain * a row reference if there is a single hit, it will be empty otherwise */ - [[nodiscard]] std::optional single() const + [[nodiscard]] std::optional single() const { - return m_impl ? m_impl->single() : std::optional(); + return m_impl ? m_impl->single() : std::optional(); } friend condition operator||(condition &&a, condition &&b); /**< Return a condition which is the logical OR or condition @a and @b */ @@ -327,7 +327,7 @@ namespace detail return this; } - [[nodiscard]] bool test(row_handle r) const override + [[nodiscard]] bool test(const_row_handle r) const override { return r[m_item_ix].empty(); } @@ -354,7 +354,7 @@ namespace detail return this; } - [[nodiscard]] bool test(row_handle r) const override + [[nodiscard]] bool test(const_row_handle r) const override { return not r[m_item_ix].empty(); } @@ -378,7 +378,7 @@ namespace detail condition_impl *prepare(const category &c) override; - [[nodiscard]] bool test(row_handle r) const override + [[nodiscard]] bool test(const_row_handle r) const override { return m_single_hit.has_value() ? *m_single_hit == r : r[m_item_ix].compare(m_value, m_icase) == 0; } @@ -388,7 +388,7 @@ namespace detail os << m_item_name << (m_icase ? "^ " : " ") << " == " << m_value; } - [[nodiscard]] std::optional single() const override + [[nodiscard]] std::optional single() const override { return m_single_hit; } @@ -411,7 +411,7 @@ namespace detail uint16_t m_item_ix = 0; bool m_icase = false; item_value m_value; - std::optional m_single_hit; + std::optional m_single_hit; }; struct key_equals_or_empty_condition_impl : public condition_impl @@ -431,7 +431,7 @@ namespace detail return this; } - [[nodiscard]] bool test(row_handle r) const override + [[nodiscard]] bool test(const_row_handle r) const override { bool result = false; if (m_single_hit.has_value()) @@ -446,7 +446,7 @@ namespace detail os << '(' << m_item_name << (m_icase ? "^ " : " ") << " == " << m_value << " OR " << m_item_name << " IS NULL)"; } - [[nodiscard]] std::optional single() const override + [[nodiscard]] std::optional single() const override { return m_single_hit; } @@ -469,7 +469,7 @@ namespace detail uint16_t m_item_ix = 0; item_value &m_value; bool m_icase = false; - std::optional m_single_hit; + std::optional m_single_hit; }; struct key_compare_condition_impl : public condition_impl @@ -489,7 +489,7 @@ namespace detail return this; } - [[nodiscard]] bool test(row_handle r) const override + [[nodiscard]] bool test(const_row_handle r) const override { return m_compare(r, m_icase); } @@ -502,7 +502,7 @@ namespace detail std::string m_item_name; uint16_t m_item_ix = 0; bool m_icase = false; - std::function m_compare; + std::function m_compare; std::string m_str; }; @@ -520,7 +520,7 @@ namespace detail return this; } - [[nodiscard]] bool test(row_handle r) const override + [[nodiscard]] bool test(const_row_handle r) const override { auto txt = r[m_item_ix].get(); return std::regex_match(txt.begin(), txt.end(), mRx); @@ -546,7 +546,7 @@ namespace detail { } - [[nodiscard]] bool test(row_handle r) const override + [[nodiscard]] bool test(const_row_handle r) const override { auto &c = r.get_category(); @@ -578,7 +578,7 @@ namespace detail { } - [[nodiscard]] bool test(row_handle r) const override + [[nodiscard]] bool test(const_row_handle r) const override { auto &c = r.get_category(); @@ -648,7 +648,7 @@ namespace detail condition_impl *prepare(const category &c) override; - [[nodiscard]] bool test(row_handle r) const override; + [[nodiscard]] bool test(const_row_handle r) const override; void str(std::ostream &os) const override { @@ -668,9 +668,9 @@ namespace detail os << ')'; } - [[nodiscard]] std::optional single() const override + [[nodiscard]] std::optional single() const override { - std::optional result; + std::optional result; for (auto sub : m_sub) { @@ -695,7 +695,7 @@ namespace detail static condition_impl *combine_equal(std::vector &subs, or_condition_impl *oc); std::vector m_sub; - std::optional m_single; // Potential result of index lookup + std::optional m_single; // Potential result of index lookup }; struct or_condition_impl : public condition_impl @@ -731,7 +731,7 @@ namespace detail condition_impl *prepare(const category &c) override; - [[nodiscard]] bool test(row_handle r) const override + [[nodiscard]] bool test(const_row_handle r) const override { bool result = false; @@ -762,9 +762,9 @@ namespace detail os << ')'; } - [[nodiscard]] std::optional single() const override + [[nodiscard]] std::optional single() const override { - std::optional result; + std::optional result; for (auto sub : m_sub) { @@ -807,7 +807,7 @@ namespace detail return this; } - [[nodiscard]] bool test(row_handle r) const override + [[nodiscard]] bool test(const_row_handle r) const override { return not mA->test(r); } @@ -863,26 +863,6 @@ inline condition operator or(condition &&a, condition &&b) return condition(new detail::key_equals_or_empty_condition_impl(ci)); } - if (typeid(*a.m_impl) == typeid(detail::key_equals_number_condition_impl) and - typeid(*b.m_impl) == typeid(detail::key_is_empty_condition_impl)) - { - auto ci = static_cast(a.m_impl); - auto ce = static_cast(b.m_impl); - - if (ci->m_item_name == ce->m_item_name) - return condition(new detail::key_equals_number_or_empty_condition_impl(ci)); - } - - if (typeid(*b.m_impl) == typeid(detail::key_equals_number_condition_impl) and - typeid(*a.m_impl) == typeid(detail::key_is_empty_condition_impl)) - { - auto ci = static_cast(b.m_impl); - auto ce = static_cast(a.m_impl); - - if (ci->m_item_name == ce->m_item_name) - return condition(new detail::key_equals_number_or_empty_condition_impl(ci)); - } - return condition(new detail::or_condition_impl(std::move(a), std::move(b))); } @@ -956,23 +936,15 @@ struct key std::string m_item_name; ///< The item name }; -template -concept Numeric = ((std::is_floating_point_v or std::is_integral_v) and not std::is_same_v); - -/** - * @brief Operator to create an equals condition based on a key @a key and a numeric value @a v - */ -template -condition operator==(const key &key, const T &v) -{ - // TODO: change key_equals_etc... to use std::variant or something - return condition(new detail::key_equals_number_condition_impl(key.m_item_name, static_cast(v))); -} /** * @brief Operator to create an equals condition based on a key @a key and a value @a value */ -inline condition operator==(const key &key, std::string_view value) + +template +concept Numeric = ((std::is_floating_point_v or std::is_integral_v) and not std::is_same_v); + +inline condition operator==(const key &key, const item_value &value) { if (not value.empty()) return condition(new detail::key_equals_condition_impl({ key.m_item_name, value })); @@ -980,29 +952,10 @@ inline condition operator==(const key &key, std::string_view value) return condition(new detail::key_is_empty_condition_impl(key.m_item_name)); } -/** - * @brief Operator to create an equals condition based on a key @a key and a value @a value - */ -template - requires std::is_same_v -inline condition operator==(const key &key, T value) -{ - return condition(new detail::key_equals_condition_impl({ key.m_item_name, value ? "y" : "n" })); -} - -/** - * @brief Operator to create a not equals condition based on a key @a key and a value @a v - */ -template -condition operator!=(const key &key, const T &v) -{ - return condition(new detail::not_condition_impl(operator==(key, v))); -} - /** * @brief Operator to create a not equals condition based on a key @a key and a value @a value */ -inline condition operator!=(const key &key, std::string_view value) +inline condition operator!=(const key &key, const item_value &value) { return condition(new detail::not_condition_impl(operator==(key, value))); } @@ -1014,9 +967,9 @@ template condition operator>(const key &key, const T &v) { return condition(new detail::key_compare_condition_impl( - key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) + key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase) { return r[item_name].compare(v) > 0; }, - cif::format(" > {}", v))); + std::format(" > {}", v))); } /** @@ -1026,7 +979,7 @@ template condition operator>=(const key &key, const T &v) { return condition(new detail::key_compare_condition_impl( - key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) + key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase) { return r[item_name].compare(v) >= 0; }, std::format(" >= {}", v))); } @@ -1038,7 +991,7 @@ template condition operator<(const key &key, const T &v) { return condition(new detail::key_compare_condition_impl( - key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) + key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase) { return r[item_name].compare(v) < 0; }, std::format(" < {}", v))); } @@ -1050,7 +1003,7 @@ template condition operator<=(const key &key, const T &v) { return condition(new detail::key_compare_condition_impl( - key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) + key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase) { return r[item_name].compare(v) <= 0; }, std::format(" <= {}", v))); } @@ -1061,7 +1014,7 @@ condition operator<=(const key &key, const T &v) inline condition operator>(const key &key, std::string_view v) { return condition(new detail::key_compare_condition_impl( - key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) + key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase) { return r[item_name].compare(v, icase) > 0; }, std::format(" > {}", v))); } @@ -1072,7 +1025,7 @@ inline condition operator>(const key &key, std::string_view v) inline condition operator>=(const key &key, std::string_view v) { return condition(new detail::key_compare_condition_impl( - key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) + key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase) { return r[item_name].compare(v, icase) >= 0; }, std::format(" >= {}", v))); } @@ -1083,7 +1036,7 @@ inline condition operator>=(const key &key, std::string_view v) inline condition operator<(const key &key, std::string_view v) { return condition(new detail::key_compare_condition_impl( - key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) + key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase) { return r[item_name].compare(v, icase) < 0; }, std::format(" < {}", v))); } @@ -1094,7 +1047,7 @@ inline condition operator<(const key &key, std::string_view v) inline condition operator<=(const key &key, std::string_view v) { return condition(new detail::key_compare_condition_impl( - key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) + key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase) { return r[item_name].compare(v, icase) <= 0; }, std::format(" <= {}", v))); } diff --git a/include/cif++/forward_decl.hpp b/include/cif++/forward_decl.hpp index 8011ebc..33db857 100644 --- a/include/cif++/forward_decl.hpp +++ b/include/cif++/forward_decl.hpp @@ -42,9 +42,6 @@ class file; class parser; class row; -class row_handle; - class item; -struct item_handle; } // namespace cif diff --git a/include/cif++/item.hpp b/include/cif++/item.hpp index 38939cf..1fc5e94 100644 --- a/include/cif++/item.hpp +++ b/include/cif++/item.hpp @@ -31,10 +31,13 @@ #include "cif++/utilities.hpp" #include +#include +#include #include #include #include #include +#include #include #include #include @@ -56,7 +59,8 @@ namespace cif { -class row_handle; +class category; +class row; // -------------------------------------------------------------------- /** @brief item is a transient class that is used to pass data into rows @@ -97,8 +101,9 @@ enum class item_value_type INT, FLOAT, TEXT, - MISSING, - EMPTY // This is the real NULL in SQL terms + + INAPPLICABLE, + MISSING }; template @@ -113,12 +118,21 @@ concept FloatType = std::is_floating_point_v>; template concept StringType = (std::is_assignable_v and not std::is_integral_v and not std::is_floating_point_v); +// -------------------------------------------------------------------- + +/// \cond +template +inline constexpr bool is_optional_v = false; +template +inline constexpr bool is_optional_v> = true; +/// \endcond + class item_value { public: item_value() noexcept { - m_data.m_type = item_value_type::EMPTY; + m_data.m_type = item_value_type::MISSING; } item_value(item_value_type type) noexcept @@ -134,14 +148,17 @@ class item_value case item_value_type::BOOLEAN: m_data.m_value = rhs.m_data.m_value.m_boolean; break; case item_value_type::INT: m_data.m_value = rhs.m_data.m_value.m_integer; break; case item_value_type::FLOAT: m_data.m_value = rhs.m_data.m_value.m_float; break; - case item_value_type::TEXT: m_data.m_value = rhs.m_data.sv(); break; + case item_value_type::TEXT: + m_data.m_len = rhs.m_data.m_len; + m_data.m_value = rhs.m_data.sv(); + break; default: break; } } item_value(std::nullptr_t) { - m_data.m_type = item_value_type::EMPTY; + m_data.m_type = item_value_type::MISSING; } template @@ -191,8 +208,14 @@ class item_value template item_value(std::optional v) - : item_value(v.has_value() ? *v : nullptr) { + if (v.has_value()) + { + item_value iv{ *v }; + swap(*this, iv); + } + else + m_data.m_type = item_value_type::MISSING; } item_value(item_value &&rhs) noexcept @@ -208,15 +231,19 @@ class item_value // -------------------------------------------------------------------- - constexpr bool is_null() const noexcept { return m_data.m_type == item_value_type::MISSING; } - constexpr bool is_empty() const noexcept { return m_data.m_type == item_value_type::EMPTY; } - constexpr bool is_string() const noexcept { return m_data.m_type == item_value_type::TEXT; } - constexpr bool is_number() const noexcept { return is_number_int() or is_number_float(); } - constexpr bool is_number_int() const noexcept { return m_data.m_type == item_value_type::INT; } - constexpr bool is_number_float() const noexcept { return m_data.m_type == item_value_type::FLOAT; } - constexpr bool is_boolean() const noexcept { return m_data.m_type == item_value_type::BOOLEAN; } + [[nodiscard]] constexpr bool is_inapplicable() const noexcept { return m_data.m_type == item_value_type::INAPPLICABLE; } + [[nodiscard]] constexpr bool is_missing() const noexcept { return m_data.m_type == item_value_type::MISSING; } + [[nodiscard]] constexpr bool is_null() const noexcept { return is_inapplicable() or is_missing(); } - constexpr item_value_type type() const { return m_data.m_type; } + [[nodiscard]] constexpr bool is_string() const noexcept { return m_data.m_type == item_value_type::TEXT; } + + [[nodiscard]] constexpr bool is_number_int() const noexcept { return m_data.m_type == item_value_type::INT; } + [[nodiscard]] constexpr bool is_number_float() const noexcept { return m_data.m_type == item_value_type::FLOAT; } + [[nodiscard]] constexpr bool is_number() const noexcept { return is_number_int() or is_number_float(); } + + [[nodiscard]] constexpr bool is_boolean() const noexcept { return m_data.m_type == item_value_type::BOOLEAN; } + + [[nodiscard]] constexpr item_value_type type() const { return m_data.m_type; } explicit operator bool() const noexcept { @@ -227,18 +254,18 @@ class item_value case item_value_type::INT: result = m_data.m_value.m_integer != 0; break; case item_value_type::FLOAT: result = m_data.m_value.m_float != 0; break; case item_value_type::TEXT: result = m_data.m_len != 0; break; - case item_value_type::MISSING: - case item_value_type::EMPTY: result = false; break; + case item_value_type::INAPPLICABLE: + case item_value_type::MISSING: result = false; break; } return result; } - bool empty() const noexcept + [[nodiscard]] bool empty() const noexcept { switch (m_data.m_type) { + case item_value_type::INAPPLICABLE: case item_value_type::MISSING: - case item_value_type::EMPTY: return true; case item_value_type::TEXT: @@ -252,12 +279,12 @@ class item_value // -------------------------------------------------------------------- template - inline std::string get() const + [[nodiscard]] inline std::string get() const { switch (m_data.m_type) { - case item_value_type::EMPTY: case item_value_type::MISSING: + case item_value_type::INAPPLICABLE: return ""; case item_value_type::TEXT: @@ -284,7 +311,7 @@ class item_value } template - std::remove_cvref_t get() const + [[nodiscard]] std::remove_cvref_t get() const { switch (m_data.m_type) { @@ -301,6 +328,9 @@ class item_value auto &&[ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.length(), v); if (ec != std::errc{}) throw std::system_error(std::make_error_code(ec)); + if (ptr != sv.data() + sv.length()) + throw std::invalid_argument("String value does not contain only an integer"); + return v; } default: @@ -309,7 +339,7 @@ class item_value } template - std::remove_cvref_t get() const + [[nodiscard]] std::remove_cvref_t get() const { switch (m_data.m_type) { @@ -326,6 +356,8 @@ class item_value auto &&[ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.length(), v); if (ec != std::errc{}) throw std::system_error(std::make_error_code(ec)); + if (ptr != sv.data() + sv.length()) + throw std::invalid_argument("String value does not contain only a floating point number"); return v; } default: @@ -334,7 +366,7 @@ class item_value } template - std::remove_cvref_t get() const + [[nodiscard]] std::remove_cvref_t get() const { switch (m_data.m_type) { @@ -352,19 +384,28 @@ class item_value } template - std::optional get() const + requires is_optional_v + [[nodiscard]] auto get() const { switch (m_data.m_type) { + case item_value_type::INAPPLICABLE: case item_value_type::MISSING: - case item_value_type::EMPTY: - return {}; + return T{}; default: - return get(); + { + auto v = get(); + return T{ v }; + } } } + [[nodiscard]] std::string str() const + { + return get(); + } + // -------------------------------------------------------------------- friend void swap(item_value &a, item_value &b) noexcept @@ -403,31 +444,28 @@ class item_value case item_value_type::INT: return m_data.m_value.m_integer == rhs.m_data.m_value.m_integer; case item_value_type::FLOAT: return m_data.m_value.m_float == rhs.m_data.m_value.m_float; case item_value_type::TEXT: return m_data.sv() == rhs.m_data.sv(); - case item_value_type::MISSING: - case item_value_type::EMPTY: return true; + case item_value_type::INAPPLICABLE: + case item_value_type::MISSING: return true; } } return false; } - int compare(const item_value &b, bool ignore_case = false) const noexcept; + [[nodiscard]] int compare(const item_value &b, bool ignore_case = false) const noexcept; - friend std::ostream operator<<(std::ostream &os, const item_value &v); + friend std::ostream &operator<<(std::ostream &os, const item_value &v); private: union value { bool m_boolean; - int64_t m_integer; + int64_t m_integer{}; double m_float; char m_local_str[8]; char *m_str; - value() - : m_integer(0) - { - } + value() = default; value(bool v) : m_boolean(v) @@ -453,7 +491,7 @@ class item_value m_str[s.length()] = 0; } else - memcpy(m_local_str, s.data(), s.length() + 1); + std::memcpy(m_local_str, s.data(), s.length() + 1); } value(item_value_type t) @@ -470,7 +508,7 @@ class item_value struct data { - item_value_type m_type = item_value_type::EMPTY; + item_value_type m_type = item_value_type::MISSING; uint32_t m_len{}; value m_value{}; @@ -481,7 +519,13 @@ class item_value } data() noexcept = default; - data(data &&) noexcept = default; + data(data &&rhs) noexcept + { + std::swap(m_type, rhs.m_type); + std::swap(m_len, rhs.m_len); + std::swap(m_value, rhs.m_value); + } + data(const data &) noexcept = delete; data &operator=(data &&) noexcept = delete; data &operator=(const data &) noexcept = delete; @@ -515,7 +559,7 @@ class item /// content the character '.', i.e. an inapplicable value. item(std::string name) : m_name(std::move(name)) - , m_value(item_value_type::EMPTY) + , m_value(item_value_type::MISSING) { } @@ -525,98 +569,31 @@ class item { } - // /// \brief constructor for an item with name \a name and as - // /// content the character '.', i.e. an inapplicable value. - // item(std::string_view name, std::nullptr_t) - // : m_name(name) - // , m_value(item_value_type::EMPTY) - // { - // } - - // /// \brief constructor for an item with name \a name and as - // /// content a single character string with content \a value - // item(std::string_view name, char value) - // : m_name(name) - // , m_value(std::string_view{ &value, 1 }) - // { - // } - - // /// \brief constructor for an item with name \a name and as - // /// content the formatted floating point value \a value - // template - // item(std::string_view name, T value) - // : m_name(name) - // , m_value(value) - // { - // } - - // /// \brief constructor for an item with name \a name and as - // /// content the formatted floating point value \a value with - // /// precision \a precision - // template - // item(std::string_view name, T value, int precision) - // : m_name(name) - // , m_value(value, precision) - // { - // } - - // /// \brief constructor for an item with name \a name and as - // /// content the formatted integral value \a value - // template - // item(const std::string_view name, T value) - // : m_name(name) - // , m_value(value) - // { - // } - - // // TODO: Perhaps introduce a real boolean type? - // /// \brief constructor for an item with name \a name and as - // /// content the formatted boolean value \a value - // template - // item(const std::string_view name, T value) - // : m_name(name) - // , m_value(value) - // { - // } - - // /// \brief constructor for an item with name \a name and as - // /// content value \a value - // item(const std::string_view name, std::string_view value) - // : m_name(name) - // , m_value(value) - // { - // } - - // /// \brief constructor for an item with name \a name and as - // /// content the optional value \a value - // template - // item(const std::string_view name, const std::optional &value) - // : m_name(name) - // , m_value(item_value_type::MISSING) - // { - // if (value.has_value()) - // m_value = *value; - // } - - // /// \brief constructor for an item with name \a name and as - // /// content the formatted floating point value \a value with - // /// precision \a precision - // template , int> = 0> - // item(std::string_view name, const std::optional &value, int precision) - // : m_name(name) - // , m_value(item_value_type::MISSING) - // { - // if (value.has_value()) - // m_value = item_value(*value, precision); - // } - /** @cond */ - item(const item &rhs) = default; - item(item &&rhs) noexcept = default; - item &operator=(const item &rhs) = default; - item &operator=(item &&rhs) noexcept = default; + item(const item &rhs) + : m_name(rhs.m_name) + , m_value(rhs.m_value) + { + } + + item(item &&rhs) + { + swap(*this, rhs); + } + + item &operator=(item rhs) noexcept + { + swap(*this, rhs); + return *this; + } /** @endcond */ + friend void swap(item &a, item &b) noexcept + { + std::swap(a.m_name, b.m_name); + std::swap(a.m_value, b.m_value); + } + const std::string &name() const { return m_name; } ///< Return the name of the item const item_value &value() const & { return m_value; } ///< Return the value of the item item_value &value() & { return m_value; } ///< Return the value of the item @@ -627,11 +604,11 @@ class item /// \brief empty means either null or unknown bool empty() const { return m_value.empty(); } - /// \brief returns true if the item contains '.' + /// \brief returns true if the item contains '.' or '?' bool is_null() const { return m_value.is_null(); } /// \brief returns true if the item contains '?' - bool is_unknown() const { return m_value.is_empty(); } + bool is_unknown() const { return m_value.is_missing(); } // /// \brief the length of the value string // std::size_t length() const { return m_value.length(); } @@ -646,7 +623,7 @@ class item return value(); } - auto operator<=>(const item &rhs) const = default; + // auto operator<=>(const item &rhs) const = default; private: std::string m_name; @@ -654,18 +631,13 @@ class item }; // -------------------------------------------------------------------- -// Transient object to access stored data - -/// \brief This is item_handle, it is used to access the data stored in item_value. +/// \brief This is item_handle, it is used to access the data stored in +/// item_value's in rows struct item_handle { public: - /** @cond */ - // conversion helper class - template - struct item_value_as; - /** @endcond */ + item_handle() = delete; /** * @brief Assign value @a value to the item referenced @@ -674,73 +646,44 @@ struct item_handle * @param value The value * @return reference to this item_handle */ + item_handle &operator=(item_value value); + + [[nodiscard]] item_value_type &value(); + [[nodiscard]] const item_value &value() const; + + [[nodiscard]] constexpr bool is_inapplicable() const noexcept { return value().type() == item_value_type::INAPPLICABLE; } + [[nodiscard]] constexpr bool is_missing() const noexcept { return value().type() == item_value_type::MISSING; } + [[nodiscard]] constexpr bool is_null() const noexcept { return is_inapplicable() or is_missing(); } + + [[nodiscard]] constexpr bool is_string() const noexcept { return value().type() == item_value_type::TEXT; } + + [[nodiscard]] constexpr bool is_number_int() const noexcept { return value().type() == item_value_type::INT; } + [[nodiscard]] constexpr bool is_number_float() const noexcept { return value().type() == item_value_type::FLOAT; } + [[nodiscard]] constexpr bool is_number() const noexcept { return is_number_int() or is_number_float(); } + + [[nodiscard]] constexpr bool is_boolean() const noexcept { return value().type() == item_value_type::BOOLEAN; } + + [[nodiscard]] auto type() const { return value().type(); } + template - item_handle &operator=(const T &value) + auto get() const { - assign_value(item{ "", value }.value()); - return *this; + return value().template get(); } - /** - * @brief Assign value @a value to the item referenced - * - * @tparam T Type of the value - * @param value The value - * @return reference to this item_handle - */ template - item_handle &operator=(T &&value) + auto as() const { - assign_value(item{ "", std::forward(value) }.value()); - return *this; + return value().template get(); } - /** - * @brief Assign value @a value to the item referenced - * - * @tparam T Type of the value - * @param value The value - * @return reference to this item_handle - */ - template - item_handle &operator=(const char (&value)[N]) + [[nodiscard]] auto str() const { - assign_value(item{ "", std::move(value) }.value()); - return *this; + return value().str(); } - /** - * @brief A method with a variable number of arguments that will be concatenated and - * assigned as a string. Use it like this: - * - * @code{.cpp} - * cif::item_handle ih; - * is.os("The result of ", 1, " * ", 42, " is of course ", 42); - * @endcode - * - * And the content will then be `The result of 1 * 42 is of course 42`. - * - * @tparam Ts Types of the parameters - * @param v The parameters to concatenate - */ - template - void os(const Ts &...v) - { - std::ostringstream ss; - ((ss << v), ...); - this->operator=(ss.str()); - } - - /** Swap contents of this and @a b */ - void swap(item_handle &b); - - /** Return the contents of this item as type @tparam T */ - template - [[nodiscard]] auto as() const -> T - { - using value_type = std::remove_cv_t>; - return item_value_as::convert(*this); - } + /** Swap contents of @a a and @a b */ + friend void swap(item_handle &a, item_handle &b) noexcept; /** Return the contents of this item as type @tparam T or, if not * set, use @a dv as the default value. @@ -748,7 +691,7 @@ struct item_handle template [[nodiscard]] auto value_or(const T &dv) const { - return empty() ? dv : this->as(); + return empty() ? dv : this->get(); } /** @@ -762,21 +705,25 @@ struct item_handle * @param icase Flag indicating if we should compare character case sensitive * @return -1, 0 or 1 */ - template - [[nodiscard]] int compare(const T &value, bool icase = true) const noexcept + + [[nodiscard]] int compare(const item_value &value, bool icase = true) const noexcept { - return item_value_as::compare(*this, value, icase); + return this->value().compare(value, icase); + } + + [[nodiscard]] int compare(const item_handle &value, bool icase = true) const noexcept + { + return compare(value.value(), icase); } /** * @brief Compare the value contained with the value @a value and * return true if both are equal. */ - template - [[nodiscard]] bool operator==(const T &value) const noexcept + [[nodiscard]] bool operator==(const item_value &value) const noexcept { // TODO: icase or not icase? - return item_value_as::compare(*this, value, true) == 0; + return this->value().compare(value) != 0; } // We may not have C++20 yet... @@ -798,29 +745,14 @@ struct item_handle */ [[nodiscard]] bool empty() const { - auto txt = text(); - return txt.empty() or (txt.length() == 1 and (txt.front() == '.' or txt.front() == '?')); + return this->value().empty(); } /** Easy way to test for an empty item */ explicit operator bool() const { return not empty(); } - /// is_null return true if the item contains '.' - [[nodiscard]] bool is_null() const - { - auto txt = text(); - return txt.length() == 1 and txt.front() == '.'; - } - - /// is_unknown returns true if the item contains '?' - [[nodiscard]] bool is_unknown() const - { - auto txt = text(); - return txt.length() == 1 and txt.front() == '?'; - } - /** Return a std::string_view for the contents */ - [[nodiscard]] std::string_view text() const; + [[nodiscard]] std::string_view text_() const; /** * @brief Construct a new item handle object @@ -828,227 +760,148 @@ struct item_handle * @param item Item index * @param row Reference to the row */ - item_handle(uint16_t item, row_handle &row) - : m_item_ix(item) - , m_row_handle(row) + item_handle(category &cat, row &row, uint16_t item_ix) + : m_category(cat) + , m_row(row) + , m_item_ix(item_ix) { } - /** A variable holding an empty item */ - CIFPP_EXPORT static const item_handle s_null_item; - - /** friend to swap two item handles */ - friend void swap(item_handle a, item_handle b) - { - a.swap(b); - } - private: - item_handle() noexcept; - + category &m_category; + row &m_row; uint16_t m_item_ix; - row_handle &m_row_handle; - void assign_value(std::string_view value); + void assign_value(item_value value); }; -// -------------------------------------------------------------------- -// Transient object to access stored data -/// \brief This is item_handle, it is used to access the data stored in item_value. - -template -struct item_handle::item_value_as and not std::is_same_v>> +struct const_item_handle { - using value_type = std::remove_reference_t>; + public: + const_item_handle() = delete; - static value_type convert(const item_handle &ref) + [[nodiscard]] const item_value &value() const; + + [[nodiscard]] constexpr bool is_inapplicable() const noexcept { return value().type() == item_value_type::INAPPLICABLE; } + [[nodiscard]] constexpr bool is_missing() const noexcept { return value().type() == item_value_type::MISSING; } + [[nodiscard]] constexpr bool is_null() const noexcept { return is_inapplicable() or is_missing(); } + + [[nodiscard]] constexpr bool is_string() const noexcept { return value().type() == item_value_type::TEXT; } + + [[nodiscard]] constexpr bool is_number_int() const noexcept { return value().type() == item_value_type::INT; } + [[nodiscard]] constexpr bool is_number_float() const noexcept { return value().type() == item_value_type::FLOAT; } + [[nodiscard]] constexpr bool is_number() const noexcept { return is_number_int() or is_number_float(); } + + [[nodiscard]] constexpr bool is_boolean() const noexcept { return value().type() == item_value_type::BOOLEAN; } + + [[nodiscard]] auto type() const { return value().type(); } + + template + auto get() const { - value_type result = {}; - - if (not ref.empty()) - { - auto txt = ref.text(); - - auto b = txt.data(); - auto e = txt.data() + txt.size(); - - std::from_chars_result r = (b + 1 < e and *b == '+' and std::isdigit(b[1])) // - ? from_chars(b + 1, e, result) - : from_chars(b, e, result); - - if (r.ec != std::errc{} or r.ptr != e) - { - result = {}; - if (cif::VERBOSE) - { - if (r.ec == std::errc::invalid_argument) - std::cerr << "Attempt to convert " << std::quoted(txt) << " into a number\n"; - else if (r.ec == std::errc::result_out_of_range) - std::cerr << "Conversion of " << std::quoted(txt) << " into a type that is too small\n"; - else - std::cerr << "Not a valid number " << std::quoted(txt) << '\n'; - } - } - } - - return result; + return value().template get(); } - static int compare(const item_handle &ref, const T &value, bool icase) noexcept + template + auto as() const { - int result = 0; - - auto txt = ref.text(); - - if (ref.empty()) - result = 1; - else - { - value_type v = {}; - - auto b = txt.data(); - auto e = txt.data() + txt.size(); - - std::from_chars_result r = (b + 1 < e and *b == '+' and std::isdigit(b[1])) - ? from_chars(b + 1, e, v) - : from_chars(b, e, v); - - if (r.ec != std::errc{} or r.ptr != e) - { - if (cif::VERBOSE) - { - if (r.ec == std::errc::invalid_argument) - std::cerr << "Attempt to convert " << std::quoted(txt) << " into a number\n"; - else if (r.ec == std::errc::result_out_of_range) - std::cerr << "Conversion of " << std::quoted(txt) << " into a type that is too small\n"; - else - std::cerr << "Not a valid number " << std::quoted(txt) << '\n'; - } - result = 1; - } - else if (std::abs(v - value) <= std::numeric_limits::epsilon()) - result = 0; - else if (v < value) - result = -1; - else if (v > value) - result = 1; - } - - return result; + return value().template get(); } + + [[nodiscard]] auto str() const + { + return value().str(); + } + + /** Return the contents of this item as type @tparam T or, if not + * set, use @a dv as the default value. + */ + template + [[nodiscard]] auto value_or(const T &dv) const + { + return empty() ? dv : this->get(); + } + + /** + * @brief Compare the contents of this item with value @a value + * optionally ignoring character case, if @a icase is true. + * Returns 0 if both are equal, -1 if this sorts before @a value + * and 1 if this sorts after @a value + * + * @tparam T Type of the value @a value + * @param value The value to compare with + * @param icase Flag indicating if we should compare character case sensitive + * @return -1, 0 or 1 + */ + + [[nodiscard]] int compare(const item_value &value, bool icase = true) const noexcept + { + return this->value().compare(value, icase); + } + + [[nodiscard]] int compare(const const_item_handle &value, bool icase = true) const noexcept + { + return compare(value.value(), icase); + } + + /** + * @brief Compare the value contained with the value @a value and + * return true if both are equal. + */ + [[nodiscard]] bool operator==(const item_value &value) const noexcept + { + // TODO: icase or not icase? + return this->value().compare(value) != 0; + } + + // We may not have C++20 yet... + + /** + * @brief Compare the value contained with the value @a value and + * return true if both are not equal. + */ + template + [[nodiscard]] bool operator!=(const T &value) const noexcept + { + return not operator==(value); + } + + /** + * @brief Returns true if the content string is empty or + * only contains '.' meaning null or '?' meaning unknown + * in a mmCIF context + */ + [[nodiscard]] bool empty() const + { + return this->value().empty(); + } + + /** Easy way to test for an empty item */ + explicit operator bool() const { return not empty(); } + + /** Return a std::string_view for the contents */ + [[nodiscard]] std::string_view text_() const; + + /** + * @brief Construct a new item handle object + * + * @param item Item index + * @param row Reference to the row + */ + const_item_handle(const category &cat, const row &row, uint16_t item_ix) + : m_category(cat) + , m_row(row) + , m_item_ix(item_ix) + { + } + + private: + const category &m_category; + const row &m_row; + uint16_t m_item_ix; }; -template -struct item_handle::item_value_as> -{ - static std::optional convert(const item_handle &ref) - { - std::optional result; - if (ref) - result = ref.as(); - return result; - } - - static int compare(const item_handle &ref, std::optional value, bool icase) noexcept - { - if (ref.empty() and not value) - return 0; - - if (ref.empty()) - return -1; - else if (not value) - return 1; - else - return ref.compare(*value, icase); - } -}; - -template -struct item_handle::item_value_as>> -{ - static bool convert(const item_handle &ref) - { - bool result = false; - if (not ref.empty()) - result = iequals(ref.text(), "y"); - return result; - } - - static int compare(const item_handle &ref, bool value, bool icase) noexcept - { - bool rv = convert(ref); - return value && rv ? 0 - : (rv < value ? -1 : 1); - } -}; - -template -struct item_handle::item_value_as -{ - static std::string convert(const item_handle &ref) - { - if (ref.empty()) - return {}; - return { ref.text().data(), ref.text().size() }; - } - - static int compare(const item_handle &ref, const char (&value)[N], bool icase) noexcept - { - return icase ? cif::icompare(ref.text(), value) : ref.text().compare(value); - } -}; - -template -struct item_handle::item_value_as>> -{ - static std::string convert(const item_handle &ref) - { - if (ref.empty()) - return {}; - return { ref.text().data(), ref.text().size() }; - } - - static int compare(const item_handle &ref, const char *value, bool icase) noexcept - { - return icase ? cif::icompare(ref.text(), value) : ref.text().compare(value); - } -}; - -template -struct item_handle::item_value_as>> -{ - static std::string convert(const item_handle &ref) - { - if (ref.empty()) - return {}; - return { ref.text().data(), ref.text().size() }; - } - - static int compare(const item_handle &ref, const std::string_view &value, bool icase) noexcept - { - return icase ? cif::icompare(ref.text(), value) : ref.text().compare(value); - } -}; - -template -struct item_handle::item_value_as>> -{ - static std::string convert(const item_handle &ref) - { - if (ref.empty()) - return {}; - return { ref.text().data(), ref.text().size() }; - } - - static int compare(const item_handle &ref, const std::string &value, bool icase) noexcept - { - return icase ? cif::icompare(ref.text(), value) : ref.text().compare(value); - } -}; - -/** @endcond */ - } // namespace cif namespace std @@ -1075,4 +928,3 @@ struct tuple_element<1, ::cif::item> }; } // namespace std - diff --git a/include/cif++/iterator.hpp b/include/cif++/iterator.hpp index c9eb94e..b10bdbb 100644 --- a/include/cif++/iterator.hpp +++ b/include/cif++/iterator.hpp @@ -91,7 +91,7 @@ class iterator_impl_base template iterator_impl_base(const iterator_impl_base &rhs) - : m_current(const_cast(rhs.m_current)) + : m_current(rhs.m_current) , m_value(rhs.m_value) , m_item_ix(rhs.m_item_ix) { @@ -99,7 +99,7 @@ class iterator_impl_base template iterator_impl_base(iterator_impl_base &rhs) - : m_current(const_cast(rhs.m_current)) + : m_current(rhs.m_current) , m_value(rhs.m_value) , m_item_ix(rhs.m_item_ix) { @@ -108,7 +108,7 @@ class iterator_impl_base template iterator_impl_base(const iterator_impl_base &rhs, const std::array &cix) - : m_current(const_cast(rhs.m_current)) + : m_current(rhs.m_current) , m_item_ix(cix) { m_value = get(std::make_index_sequence()); @@ -144,7 +144,7 @@ class iterator_impl_base return &m_value; } - operator const row_handle() const + operator const_row_handle() const { return m_current; } @@ -195,7 +195,7 @@ class iterator_impl_base return m_current ? tuple_type{ m_current[m_item_ix[Is]].template as()... } : tuple_type{}; } - row_handle m_current; + std::conditional_t m_current; tuple_type m_value; std::array m_item_ix; }; @@ -218,10 +218,11 @@ class iterator_impl_base friend class category; using category_type = std::conditional_t; + using row_type = std::conditional_t; using iterator_category = std::forward_iterator_tag; - using value_type = std::conditional_t; + using value_type = std::conditional_t; using difference_type = std::ptrdiff_t; using pointer = value_type *; using reference = value_type &; @@ -233,18 +234,18 @@ class iterator_impl_base template iterator_impl_base(const iterator_impl_base &rhs) - : m_current(const_cast(rhs.m_current)) + : m_current(rhs.m_current) { } - iterator_impl_base(category_type &cat, row *current) + iterator_impl_base(category_type &cat, row_type *current) : m_current(cat, *current) { } template iterator_impl_base(const iterator_impl_base &rhs, const std::array &) - : m_current(const_cast(rhs.m_current)) + : m_current(rhs.m_current) { } @@ -276,7 +277,7 @@ class iterator_impl_base return &m_current; } - operator const row_handle() const + operator const_row_handle() const { return m_current; } @@ -324,7 +325,7 @@ class iterator_impl_base /** @endcond */ private: - row_handle m_current; + value_type m_current; }; /** @@ -368,7 +369,7 @@ class iterator_impl_base template iterator_impl_base(iterator_impl_base &rhs) - : m_current(const_cast(rhs.m_current)) + : m_current(rhs.m_current) , m_value(rhs.m_value) , m_item_ix(rhs.m_item_ix) { @@ -377,7 +378,7 @@ class iterator_impl_base template iterator_impl_base(const iterator_impl_base &rhs, const std::array &cix) - : m_current(const_cast(rhs.m_current)) + : m_current(rhs.m_current) , m_item_ix(cix[0]) { m_value = get(); @@ -413,7 +414,7 @@ class iterator_impl_base return &m_value; } - operator const row_handle() const + operator const_row_handle() const { return m_current; } @@ -460,7 +461,7 @@ class iterator_impl_base private: [[nodiscard]] value_type get() const { - return m_current ? m_current[m_item_ix].template as() : value_type{}; + return m_current ? m_current[m_item_ix].template get() : value_type{}; } row_handle m_current; @@ -810,6 +811,11 @@ void swap(conditional_iterator_proxy_base &lhs, conditional_iterat std::swap(lhs.mCix, rhs.mCix); } +// -------------------------------------------------------------------- + +// template + + /** @endcond */ } // namespace cif diff --git a/include/cif++/model.hpp b/include/cif++/model.hpp index 46e01be..bc4f3cc 100644 --- a/include/cif++/model.hpp +++ b/include/cif++/model.hpp @@ -128,7 +128,7 @@ class atom return m_cat[{ { .name = "id", .value = m_id } }]; } - [[nodiscard]] const row_handle row() const + [[nodiscard]] const_row_handle row() const { return m_cat[{ { .name = "id", .value = m_id } }]; } @@ -142,7 +142,7 @@ class atom return result; } - [[nodiscard]] const row_handle row_aniso() const + [[nodiscard]] const_row_handle row_aniso() const { row_handle result{}; auto cat = m_db.get("atom_site_anisotrop"); @@ -189,7 +189,7 @@ class atom * @param db The datablock where the _atom_site category resides * @param row The row containing the data for this atom */ - atom(const datablock &db, const row_handle &row) + atom(const datablock &db, const_row_handle &row) : atom(std::make_shared(db, row["id"].as())) { } @@ -315,10 +315,10 @@ class atom } /// for direct access to underlying data, be careful! - [[nodiscard]] const row_handle get_row() const { return impl().row(); } + [[nodiscard]] const_row_handle get_row() const { return impl().row(); } /// for direct access to underlying data, be careful! - [[nodiscard]] const row_handle get_row_aniso() const { return impl().row_aniso(); } + [[nodiscard]] const_row_handle get_row_aniso() const { return impl().row_aniso(); } /// Return if the atom is actually a symmetry copy or the original one [[nodiscard]] bool is_symmetry_copy() const { return impl().m_symop != "1_555"; } diff --git a/include/cif++/parser.hpp b/include/cif++/parser.hpp index aa7a45c..cbfced9 100644 --- a/include/cif++/parser.hpp +++ b/include/cif++/parser.hpp @@ -166,7 +166,13 @@ class sac_parser SAVE_NAME, STOP, ITEM_NAME, - VALUE + + VALUE_INAPPLICABLE, + VALUE_UNKNOWN, + VALUE_NUMERIC_INTEGER, + VALUE_NUMERIC_FLOAT, + VALUE_CHARSTRING, + VALUE_TEXTFIELD }; static constexpr const char *get_token_name(CIFToken token) @@ -182,7 +188,15 @@ class sac_parser case CIFToken::SAVE_NAME: return "SAVE+name"; case CIFToken::STOP: return "STOP"; case CIFToken::ITEM_NAME: return "Tag"; - case CIFToken::VALUE: return "Value"; + // case CIFToken::VALUE: return "Value"; + + case CIFToken::VALUE_INAPPLICABLE: return "Inapplicable value"; + case CIFToken::VALUE_UNKNOWN: return "'Unknown' value (=null)"; + case CIFToken::VALUE_NUMERIC_INTEGER: return "Integer value"; + case CIFToken::VALUE_NUMERIC_FLOAT: return "Float value"; + case CIFToken::VALUE_CHARSTRING: return "Charstring value"; + case CIFToken::VALUE_TEXTFIELD: return "Textfield value"; + default: return "Invalid token parameter"; } } @@ -282,13 +296,17 @@ class sac_parser ItemName, TextItem, TextItemNL, + Reserved, + Value, TextItemBS, TextItemBS2, TextItemBSNL, - Reserved, - Value + Numeric_Integer, + Numeric_Float, + Numeric_Exponent1, + Numeric_Exponent2 }; std::streambuf &m_source; @@ -302,6 +320,8 @@ class sac_parser // token buffer std::vector m_token_buffer; std::string_view m_token_value; + int64_t m_token_value_int; + double m_token_value_float; /** @endcond */ }; diff --git a/include/cif++/row.hpp b/include/cif++/row.hpp index d778dd5..146ad01 100644 --- a/include/cif++/row.hpp +++ b/include/cif++/row.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -88,12 +89,13 @@ namespace cif class category; class row_handle; +class const_row_handle; + namespace cql { struct connection_impl; } - namespace detail { @@ -103,7 +105,7 @@ namespace detail { static constexpr std::size_t N = sizeof...(C); - get_row_result(const row_handle &r, std::array &&items) + get_row_result(const const_row_handle &r, std::array &&items) : m_row(r) , m_items(std::move(items)) { @@ -127,7 +129,7 @@ namespace detail return std::tuple{ m_row[m_items[Is]].template get()... }; } - const row_handle &m_row; + const const_row_handle &m_row; std::array m_items; }; @@ -179,7 +181,9 @@ class row : public std::vector */ item_value *get(uint16_t ix) { - return ix < size() ? &data()[ix] : nullptr; + if (ix >= size()) + resize(ix + 1); + return &data()[ix]; } /** @@ -190,14 +194,14 @@ class row : public std::vector return ix < size() ? &data()[ix] : nullptr; } -// private: + // private: friend class category; friend class category_index; template friend class iterator_impl_base; - void append(uint16_t ix, item_value &&iv) + void append(uint16_t ix, item_value iv) { if (ix >= size()) resize(ix + 1); @@ -221,7 +225,8 @@ class row_handle { public: /** @cond */ - friend struct item_handle; + template + friend struct item_handle_base; friend class category; friend class category_index; friend class row_initializer; @@ -230,24 +235,24 @@ class row_handle friend class iterator_impl_base; row_handle() = default; + virtual ~row_handle() = default; row_handle(const row_handle &) = default; row_handle(row_handle &&) = default; - row_handle &operator=(const row_handle &) = default; row_handle &operator=(row_handle &&) = default; /** @endcond */ /// \brief constructor taking a category @a cat and a row @a r - row_handle(const category &cat, const row &r) - : m_category(const_cast(&cat)) - , m_row(const_cast(&r)) + row_handle(category &cat, row &r) + : m_category(&cat) + , m_row(&r) { } /// \brief return the category this row belongs to - [[nodiscard]] const category &get_category() const + [[nodiscard]] category &get_category() const { return *m_category; } @@ -270,58 +275,31 @@ class row_handle return not empty(); } + /// \brief return the count of the items + [[nodiscard]] size_t size() const { return m_row->size(); } + /// \brief return a cif::item_handle to the item in item @a item_ix item_handle operator[](uint16_t item_ix) { - return empty() ? item_handle::s_null_item : item_handle(item_ix, *this); + return { *m_category, *m_row, item_ix }; } - /// \brief return a const cif::item_handle to the item in item @a item_ix - const item_handle operator[](uint16_t item_ix) const + /// \brief return a cif::item_handle to the item in item @a item_ix + const_item_handle operator[](uint16_t item_ix) const { - return empty() ? item_handle::s_null_item : item_handle(item_ix, const_cast(*this)); + return { *m_category, *m_row, item_ix }; } /// \brief return a cif::item_handle to the item in the item named @a item_name item_handle operator[](std::string_view item_name) { - return empty() ? item_handle::s_null_item : item_handle(add_item(item_name), *this); + return { *m_category, *m_row, get_item_ix(item_name) }; } - /// \brief return a const cif::item_handle to the item in the item named @a item_name - const item_handle operator[](std::string_view item_name) const + /// \brief return a cif::item_handle to the item in the item named @a item_name + const_item_handle operator[](std::string_view item_name) const { - return empty() ? item_handle::s_null_item : item_handle(get_item_ix(item_name), const_cast(*this)); - } - - /// \brief Return an object that can be used in combination with cif::tie - /// to assign the values for the items @a items - template - [[nodiscard]] auto get(C... items) const - { - return detail::get_row_result(*this, { get_item_ix(items)... }); - } - - /// \brief Return a tuple of values of types @a Ts for the items @a items - template - std::tuple get(C... items) const - requires(sizeof...(Ts) == sizeof...(C) and sizeof...(C) != 1) - { - return detail::get_row_result(*this, { get_item_ix(items)... }); - } - - /// \brief Get the value of item @a item cast to type @a T - template - T get(const char *item) const - { - return operator[](get_item_ix(item)).template get(); - } - - /// \brief Get the value of item @a item cast to type @a T - template - [[nodiscard]] T get(std::string_view item) const - { - return operator[](get_item_ix(item)).template get(); + return { *m_category, *m_row, get_item_ix(item_name) }; } /// \brief assign each of the items named in @a values to their respective value @@ -360,39 +338,185 @@ class row_handle void assign(uint16_t item, item_value value, bool updateLinked, bool validate = true); + /// \brief Return an object that can be used in combination with cif::tie + /// to assign the values for the items @a items + template + [[nodiscard]] auto get(C... items) const + { + return detail::get_row_result(*this, { get_item_ix(items)... }); + } + + /// \brief Return a tuple of values of types @a Ts for the items @a items + template + std::tuple get(C... items) const + requires(sizeof...(Ts) == sizeof...(C) and sizeof...(C) != 1) + { + return detail::get_row_result(*this, { get_item_ix(items)... }); + } + + /// \brief Get the value of item @a item cast to type @a T + template + [[nodiscard]] T get(std::string_view item) const + { + return operator[](get_item_ix(item)).template get(); + } + /// \brief compare two rows bool operator==(const row_handle &rhs) const { return m_category == rhs.m_category and m_row == rhs.m_row; } /// \brief compare two rows bool operator!=(const row_handle &rhs) const { return m_category != rhs.m_category or m_row != rhs.m_row; } - private: + protected: [[nodiscard]] uint16_t get_item_ix(std::string_view name) const; [[nodiscard]] std::string_view get_item_name(uint16_t ix) const; - uint16_t add_item(std::string_view name); - friend cql::connection_impl; - row *get_row() + [[nodiscard]] auto get_row() const { return m_row; } - [[nodiscard]] const row *get_row() const - { - return m_row; - } + // void swap(uint16_t item, row_handle &r) noexcept(false); + // { + // if (not m_category) + // throw std::runtime_error("uninitialized row"); + // + // m_category->swap_item(item, *this, b); + // } + + category *m_category = nullptr; + row *m_row = nullptr; + + private: + uint16_t add_item(std::string_view name); void assign(const item &i, bool updateLinked) { assign(i.name(), i.value(), updateLinked); } +}; - void swap(uint16_t item, row_handle &r) noexcept(false); +class const_row_handle +{ + public: + /** @cond */ + template + friend struct item_handle_base; + friend class category; + friend class category_index; + friend class row_initializer; - category *m_category = nullptr; - row *m_row = nullptr; + template + friend class iterator_impl_base; + + const_row_handle() = default; + virtual ~const_row_handle() = default; + + const_row_handle(const const_row_handle &) = default; + const_row_handle(const_row_handle &&) = default; + const_row_handle &operator=(const const_row_handle &) = default; + const_row_handle &operator=(const_row_handle &&) = default; + + /** @endcond */ + + /// \brief constructor taking a category @a cat and a row @a r + const_row_handle(const category &cat, const row &r) + : m_category(&cat) + , m_row(&r) + { + } + + /// \brief return the category this row belongs to + [[nodiscard]] const category &get_category() const + { + return *m_category; + } + + /// \brief return the row ID + [[nodiscard]] int64_t row_id() const + { + return reinterpret_cast(m_row); + } + + /// \brief Return true if the row is empty or uninitialised + [[nodiscard]] bool empty() const + { + return m_category == nullptr or m_row == nullptr; + } + + /// \brief convenience method to test for empty() + explicit operator bool() const + { + return not empty(); + } + + /// \brief return the count of the items + [[nodiscard]] size_t size() const { return m_row->size(); } + + /// \brief return a cif::item_handle to the item in item @a item_ix + const_item_handle operator[](uint16_t item_ix) const + { + return { *m_category, *m_row, item_ix }; + } + + /// \brief return a cif::item_handle to the item in the item named @a item_name + const_item_handle operator[](std::string_view item_name) const + { + return { *m_category, *m_row, get_item_ix(item_name) }; + } + + /// \brief Return an object that can be used in combination with cif::tie + /// to assign the values for the items @a items + template + [[nodiscard]] auto get(C... items) const + { + return detail::get_row_result(*this, { get_item_ix(items)... }); + } + + /// \brief Return a tuple of values of types @a Ts for the items @a items + template + std::tuple get(C... items) const + requires(sizeof...(Ts) == sizeof...(C) and sizeof...(C) != 1) + { + return detail::get_row_result(*this, { get_item_ix(items)... }); + } + + /// \brief Get the value of item @a item cast to type @a T + template + [[nodiscard]] T get(std::string_view item) const + { + return operator[](get_item_ix(item)).template get(); + } + + /// \brief compare two rows + bool operator==(const const_row_handle &rhs) const { return m_category == rhs.m_category and m_row == rhs.m_row; } + + /// \brief compare two rows + bool operator!=(const const_row_handle &rhs) const { return m_category != rhs.m_category or m_row != rhs.m_row; } + + protected: + [[nodiscard]] uint16_t get_item_ix(std::string_view name) const; + [[nodiscard]] std::string_view get_item_name(uint16_t ix) const; + + friend cql::connection_impl; + + [[nodiscard]] auto get_row() const + { + return m_row; + } + + // void swap(uint16_t item, const_row_handle &r) noexcept(false); + // { + // if (not m_category) + // throw std::runtime_error("uninitialized row"); + // + // m_category->swap_item(item, *this, b); + // } + + const category *m_category = nullptr; + const row *m_row = nullptr; }; // -------------------------------------------------------------------- @@ -433,7 +557,7 @@ class row_initializer : public std::vector } /// \brief constructor taking the values of an existing row - row_initializer(row_handle rh); + row_initializer(const_row_handle rh); /// \brief set the value for item name @a name to @a value void set_value(std::string name, item_value value); diff --git a/include/cif++/validate.hpp b/include/cif++/validate.hpp index 2598c4d..1561aef 100644 --- a/include/cif++/validate.hpp +++ b/include/cif++/validate.hpp @@ -326,11 +326,13 @@ struct item_validator return iequals(m_item_name, rhs.m_item_name); } - /// @brief Validate the value in @a value for this item - /// Will throw a std::system_error exception if it fails - void operator()(std::string_view value) const; + /// @brief Validate value @a value, throws if invalid + void validate_value(const item_value &value) const; - /// @brief A more gentle version of value validation + /// @brief Validate value @a value and return potential error in @a ec + bool validate_value(const item_value &value, std::error_code &ec) const noexcept; + + /// @brief Validate value @a value and return potential error in @a ec bool validate_value(std::string_view value, std::error_code &ec) const noexcept; }; diff --git a/src/category.cpp b/src/category.cpp index c0ded0c..296d26e 100644 --- a/src/category.cpp +++ b/src/category.cpp @@ -97,10 +97,11 @@ class row_comparator int d = 0; for (const auto &[k, f] : m_comparator) { - std::string_view ka = rha[k].text(); - std::string_view kb = rhb[k].text(); + // std::string_view ka = rha[k].text(); + // std::string_view kb = rhb[k].text(); - d = f(ka, kb); + // d = f(ka, kb); + d = rha[k].value().compare(rhb[k].value()); if (d != 0) break; @@ -109,33 +110,35 @@ class row_comparator return d; } - int operator()(const category &cat, const category::key_type &a, const row *b) const - { - assert(b); + // int operator()(const category &cat, const category::key_type &a, const row *b) const + // { + // assert(b); - row_handle rhb(cat, *b); + // row_handle rhb(cat, *b); - int d = 0; - auto ai = a.begin(); + // int d = 0; + // auto ai = a.begin(); - for (const auto &[k, f] : m_comparator) - { - assert(ai != a.end()); + // for (const auto &[k, f] : m_comparator) + // { + // // assert(ai != a.end()); - std::string_view ka = ai->value; - std::string_view kb = rhb[k].text(); + // // std::string_view ka = ai->value; + // // std::string_view kb = rhb[k].text(); - if (not(ai->may_be_null and rhb[k].empty())) - d = f(ka, kb); + // // if (not(ai->may_be_null and rhb[k].empty())) + // // d = f(ka, kb); - if (d != 0) - break; + // d = rha[k].value().compare(rhb[k].value()); - ++ai; - } + // if (d != 0) + // break; - return d; - } + // ++ai; + // } + + // return d; + // } private: using compareFunc = std::function; @@ -368,34 +371,35 @@ row *category_index::find(const category &cat, row *k) const row *category_index::find_by_value(const category &cat, const category::key_type &k) const { - // sort the values in k first +return nullptr; + // // sort the values in k first - category::key_type k2; - for (auto &f : cat.key_item_indices()) - { - auto fld = cat.get_item_name(f); + // category::key_type k2; + // for (auto &f : cat.key_item_indices()) + // { + // auto fld = cat.get_item_name(f); - auto ki = std::ranges::find_if(k, [&fld](auto &i) - { return i.name == fld; }); - if (ki == k.end()) - k2.emplace_back(std::string{ fld }, ""); - else - k2.emplace_back(*ki); - } + // auto ki = std::ranges::find_if(k, [&fld](auto &i) + // { return i.name == fld; }); + // if (ki == k.end()) + // k2.emplace_back(std::string{ fld }, ""); + // else + // k2.emplace_back(*ki); + // } - const entry *r = m_root; - while (r != nullptr) - { - int d = m_row_comparator(cat, k2, r->m_row); - if (d < 0) - r = r->m_left; - else if (d > 0) - r = r->m_right; - else - break; - } + // const entry *r = m_root; + // while (r != nullptr) + // { + // int d = m_row_comparator(cat, k2, r->m_row); + // if (d < 0) + // r = r->m_left; + // else if (d > 0) + // r = r->m_right; + // else + // break; + // } - return r ? r->m_row : nullptr; + // return r ? r->m_row : nullptr; } void category_index::insert(category &cat, row *k) @@ -422,7 +426,7 @@ category_index::entry *category_index::insert(category &cat, entry *h, row *v) for (auto col : cat.key_items()) { if (rh[col]) - os << col << ": " << std::quoted(rh[col].text()) << "; "; + os << col << ": " << std::quoted(rh[col].str()) << "; "; } throw duplicate_key_error("Duplicate Key violation, cat: " + cat.name() + " values: " + os.str()); @@ -870,7 +874,7 @@ bool category::is_valid() const seen = true; std::error_code ec; - iv->validate_value(vi->text(), ec); + iv->validate_value(*vi, ec); if (ec != std::errc{}) { @@ -990,6 +994,25 @@ row_handle category::operator[](const key_type &key) // -------------------------------------------------------------------- +const_row_handle category::operator[](const key_type &key) const +{ + const_row_handle result{}; + + if (not empty()) + { + if (m_index == nullptr) + throw std::logic_error("Category " + m_name + " does not have an index"); + + auto row = m_index->find_by_value(*this, key); + if (row != nullptr) + result = { *this, *row }; + } + + return result; +} + +// -------------------------------------------------------------------- + condition category::get_parents_condition(row_handle rh, const category &parentCat) const { if (m_validator == nullptr or m_cat_validator == nullptr) @@ -1010,12 +1033,12 @@ condition category::get_parents_condition(row_handle rh, const category &parentC for (std::size_t ix = 0; ix < link->m_child_keys.size(); ++ix) { - auto childValue = rh[link->m_child_keys[ix]]; + auto childValue = rh[link->m_child_keys[ix]].value(); if (childValue.empty()) continue; - cond = std::move(cond) and key(link->m_parent_keys[ix]) == childValue.text(); + cond = std::move(cond) and key(link->m_parent_keys[ix]) == childValue; } result = std::move(result) or std::move(cond); @@ -1055,14 +1078,14 @@ condition category::get_children_condition(row_handle rh, const category &childC auto childKey = link->m_child_keys[ix]; auto parentKey = link->m_parent_keys[ix]; - auto parentValue = rh[parentKey]; + auto parentValue = rh[parentKey].value(); if (parentValue.empty()) cond = std::move(cond) and key(childKey) == null; else if (link->m_parent_keys.size() > 1 and not mandatoryChildItems.contains(childKey)) - cond = std::move(cond) and (key(childKey) == parentValue.text() or key(childKey) == null); + cond = std::move(cond) and (key(childKey) == parentValue or key(childKey) == null); else - cond = std::move(cond) and key(childKey) == parentValue.text(); + cond = std::move(cond) and key(childKey) == parentValue; } result = std::move(result) or std::move(cond); @@ -1405,7 +1428,7 @@ void category::update_value(const std::vector &rows, std::string_vie { for (auto row : rows) { - std::string value{ value_provider(row[item_name].text()) }; + auto value{ value_provider(row[item_name].value()) }; std::error_code ec; col.m_validator->validate_value(value, ec); @@ -1417,8 +1440,8 @@ void category::update_value(const std::vector &rows, std::string_vie // update and see if we need to update any child categories that depend on this value for (auto parent : rows) { - std::string oldValue{ parent[item_name].text() }; - std::string value{ value_provider(oldValue) }; + auto oldValue{ parent[item_name].value() }; + auto value{ value_provider(oldValue) }; update_value(parent.get_row(), colIx, value, false, false); @@ -1441,7 +1464,7 @@ void category::update_value(const std::vector &rows, std::string_vie cond = std::move(cond) && key(ck) == oldValue; } else - cond = std::move(cond) && key(ck) == parent[pk].text(); + cond = std::move(cond) && key(ck) == parent[pk].value(); } auto children = childCat->find(std::move(cond)); @@ -1465,7 +1488,7 @@ void category::update_value(const std::vector &rows, std::string_vie std::string pk = linked->m_parent_keys[ix]; std::string ck = linked->m_child_keys[ix]; - cond_c = std::move(cond_c) && key(pk) == child[ck].text(); + cond_c = std::move(cond_c) && key(pk) == child[ck].value(); } auto parents = find(std::move(cond_c)); @@ -1486,7 +1509,7 @@ void category::update_value(const std::vector &rows, std::string_vie if (pk == item_name) check = std::move(check) && key(ck) == value; else - check = std::move(check) && key(ck) == parent[pk].text(); + check = std::move(check) && key(ck) == parent[pk].value(); } if (childCat->contains(std::move(check))) // phew..., narrow escape @@ -1527,11 +1550,13 @@ void category::update_value(row *row, uint16_t item, item_value value, bool upda if (ival != nullptr and *ival == value) return; + auto oldValue = *ival; + m_dirty = true; // check the value if (col.m_validator and validate) - col.m_validator->operator()(value); + col.m_validator->validate_value(value); // If the item is part of the Key for this category, remove it from the index // before updating @@ -1580,11 +1605,11 @@ void category::update_value(row *row, uint16_t item, item_value value, bool upda if (pk == iv->m_item_name) { childItemName = ck; - cond = std::move(cond) and key(ck) == oldStrValue; + cond = std::move(cond) and key(ck) == oldValue; } else { - std::string_view pk_value = rh[pk].text(); + auto pk_value = rh[pk].value(); if (pk_value.empty()) cond = std::move(cond) and key(ck) == null; else @@ -1616,7 +1641,7 @@ void category::update_value(row *row, uint16_t item, item_value value, bool upda cond_n = std::move(cond_n) and key(ck) == value; else { - std::string_view pk_value = rh[pk].text(); + auto pk_value = rh[pk].value(); if (pk_value.empty()) cond_n = std::move(cond_n) and key(ck) == null; else @@ -1651,7 +1676,7 @@ row *category::clone_row(const row &r) if (not i) continue; - result->append(ix, { i.text() }); + result->append(ix, i); } } catch (...) @@ -1684,7 +1709,7 @@ row_handle category::create_copy(row_handle r) { auto i = r.m_row->get(ix); if (i != nullptr) - items.emplace_back(m_items[ix].m_name, i->text()); + items.emplace_back(m_items[ix].m_name, *i); } if (m_cat_validator and m_cat_validator->m_keys.size() == 1) @@ -1742,7 +1767,7 @@ category::iterator category::insert_impl(const_iterator pos, row *n) auto i = n->get(ix); if (i != nullptr) { - iv->operator()(i->value()); + iv->validate_value(*i); seen = true; } @@ -2055,11 +2080,11 @@ void category::write_cif(std::ostream &os, const std::vector &order, b if (v == nullptr) continue; - if (v->text().find('\n') == std::string_view::npos) + if (v->str().find('\n') == std::string_view::npos) { - std::size_t l = v->text().length(); + std::size_t l = v->str().length(); - if (not sac_parser::is_unquoted_string(v->text())) + if (not sac_parser::is_unquoted_string(v->str())) l += 2; if (l > 132) @@ -2079,10 +2104,10 @@ void category::write_cif(std::ostream &os, const std::vector &order, b { std::size_t w = itemWidths[cix]; - std::string_view s; + std::string s; auto iv = r->get(cix); if (iv != nullptr) - s = iv->text(); + s = iv->str(); if (s.empty()) s = "?"; @@ -2134,10 +2159,10 @@ void category::write_cif(std::ostream &os, const std::vector &order, b if (not right_aligned[cix]) continue; - std::string_view s; + std::string s; auto iv = m_head->get(cix); if (iv != nullptr) - s = iv->text(); + s = iv->str(); if (s.empty()) s = "?"; @@ -2160,10 +2185,10 @@ void category::write_cif(std::ostream &os, const std::vector &order, b os << m_name << '.'; os << col.m_name << std::string(l - col.m_name.length() - m_name.length() - 2, ' '); - std::string_view s; + std::string s; auto iv = m_head->get(cix); if (iv != nullptr) - s = iv->text(); + s = iv->str(); if (s.empty()) s = "?"; @@ -2262,7 +2287,7 @@ void category::write_delimited(std::ostream &os, const std::vector &or if (v == nullptr) continue; - size_t l = get_line(v->text()).length(); + size_t l = get_line(v->str()).length(); if (itemWidths[ix] < l) itemWidths[ix] = l; } @@ -2324,11 +2349,11 @@ void category::write_delimited(std::ostream &os, const std::vector &or std::size_t w = itemWidths[cix]; - std::string_view s; + std::string s; auto iv = r->get(cix); if (iv != nullptr) - s = iv->text(); + s = iv->str(); if (s == "?" or s == ".") s = ""; @@ -2383,7 +2408,7 @@ void category::write_markdown(std::ostream &os, const std::vector &ord if (v == nullptr) continue; - size_t l = v->text().length(); + size_t l = v->str().length(); if (itemWidths[ix] < l) itemWidths[ix] = l; } @@ -2432,11 +2457,11 @@ void category::write_markdown(std::ostream &os, const std::vector &ord std::size_t w = itemWidths[cix]; - std::string_view s; + std::string s; auto iv = r->get(cix); if (iv != nullptr) - s = iv->text(); + s = iv->str(); if (s == "?" or s == ".") s = ""; @@ -2509,7 +2534,7 @@ void category::write_table(std::ostream &os, const std::vector &order, if (v == nullptr) continue; - size_t l = v->text().length(); + size_t l = v->str().length(); if (itemWidths[ix] < l) itemWidths[ix] = l; } @@ -2565,11 +2590,11 @@ void category::write_table(std::ostream &os, const std::vector &order, std::size_t w = itemWidths[cix]; - std::string_view s; + std::string s; auto iv = r->get(cix); if (iv != nullptr) - s = iv->text(); + s = iv->str(); if (s == "?" or s == ".") s = ""; @@ -2664,7 +2689,7 @@ bool category::operator==(const category &rhs) const // a.reorderByIndex(); // b.reorderByIndex(); - auto rowEqual = [&](const row_handle &a, const row_handle &b) + auto rowEqual = [&](const_row_handle &a, const_row_handle &b) { int d = 0; @@ -2675,7 +2700,7 @@ bool category::operator==(const category &rhs) const std::tie(item_name, compare) = item_names[kix]; - d = compare(a[item_name].text(), b[item_name].text()); + d = a[item_name].compare(b[item_name]); if (d != 0) break; @@ -2706,14 +2731,14 @@ bool category::operator==(const category &rhs) const // make it an option to compare unapplicable to empty or something - auto ta = ra[item_name].text(); - if (ta == "." or ta == "?") - ta = ""; - auto tb = rb[item_name].text(); - if (tb == "." or tb == "?") - tb = ""; + // auto ta = ra[item_name].text(); + // if (ta == "." or ta == "?") + // ta = ""; + // auto tb = rb[item_name].text(); + // if (tb == "." or tb == "?") + // tb = ""; - if (compare(ta, tb) != 0) + if (ra[item_name].compare(rb[item_name]) != 0) return false; } diff --git a/src/compound.cpp b/src/compound.cpp index d2c1e57..2f1479c 100644 --- a/src/compound.cpp +++ b/src/compound.cpp @@ -567,9 +567,9 @@ compound *local_compound_factory_impl::construct_compound(const datablock &rdb, { "atom_id", atom_id }, { "type_symbol", type_symbol }, { "charge", charge }, - { "model_Cartn_x", x.has_value() ? x : xi, 3 }, - { "model_Cartn_y", y.has_value() ? y : yi, 3 }, - { "model_Cartn_z", z.has_value() ? z : zi, 3 }, + { "model_Cartn_x", x.has_value() ? x : xi/* , 3 */ }, + { "model_Cartn_y", y.has_value() ? y : yi/* , 3 */ }, + { "model_Cartn_z", z.has_value() ? z : zi/* , 3 */ }, { "pdbx_ordinal", ord++ } }); formal_charge += charge; diff --git a/src/condition.cpp b/src/condition.cpp index b606eda..35336de 100644 --- a/src/condition.cpp +++ b/src/condition.cpp @@ -78,7 +78,7 @@ namespace detail // return this; // } // - // bool test(row_handle r) const override + // bool test(const_row_handle r) const override // { // return m_single_hit == r; // } @@ -126,21 +126,6 @@ namespace detail return this; } - condition_impl *key_equals_number_condition_impl::prepare(const category &c) - { - m_item_ix = c.get_item_ix(m_item_name); - - if (c.get_cat_validator() != nullptr and - c.key_item_indices().contains(m_item_ix) and - c.key_item_indices().size() == 1) - { - item v(m_item_name, m_value); - m_single_hit = c[{ { m_item_name, std::string{ v.value() }, false } }]; - } - - return this; - } - bool found_in_range(condition_impl *c, std::vector::iterator b, std::vector::iterator e) { bool result = true; @@ -237,17 +222,6 @@ namespace detail continue; } - if (auto s = dynamic_cast(sub); s != nullptr) - { - if (keys.contains(s->m_item_name)) - { - item v{ s->m_item_name, s->m_value }; - lookup.emplace_back(s->m_item_name, std::string{ v.value() }); - subs.emplace_back(sub); - } - continue; - } - if (auto s = dynamic_cast(sub); s != nullptr) { if (keys.contains(s->m_item_name)) @@ -258,17 +232,6 @@ namespace detail } continue; } - - if (auto s = dynamic_cast(sub); s != nullptr) - { - if (keys.contains(s->m_item_name)) - { - item v{ s->m_item_name, s->m_value }; - lookup.emplace_back(s->m_item_name, std::string{ v.value() }, true); - subs.emplace_back(sub); - } - continue; - } } if (lookup.size() == keys.size()) @@ -283,7 +246,7 @@ namespace detail return this; } - bool and_condition_impl::test(row_handle r) const + bool and_condition_impl::test(const_row_handle r) const { bool result = true; diff --git a/src/dictionary_parser.cpp b/src/dictionary_parser.cpp index 4261594..dc6996c 100644 --- a/src/dictionary_parser.cpp +++ b/src/dictionary_parser.cpp @@ -156,7 +156,7 @@ class dictionary_parser : public parser match(CIFToken::ITEM_NAME); } - while (m_lookahead == CIFToken::VALUE) + while (m_lookahead >= CIFToken::VALUE_INAPPLICABLE) { cat->emplace({}); auto row = cat->back(); @@ -164,7 +164,7 @@ class dictionary_parser : public parser for (auto item_name : item_names) { row[item_name] = m_token_value; - match(CIFToken::VALUE); + match(m_lookahead); } } @@ -184,7 +184,7 @@ class dictionary_parser : public parser cat->emplace({}); cat->back()[item_name] = m_token_value; - match(CIFToken::VALUE); + match(m_lookahead >= CIFToken::VALUE_INAPPLICABLE ? m_lookahead : CIFToken::VALUE_CHARSTRING); } } @@ -257,7 +257,7 @@ class dictionary_parser : public parser auto vi = std::ranges::find(ivs, item_validator{ item_name }); if (vi == ivs.end()) - ivs.emplace_back(item_name, iequals(mandatory, "yes"), tv, ess, defaultValue, cat_name, aliases); + ivs.push_back(item_validator{ item_name, iequals(mandatory, "yes"), tv, ess, defaultValue, cat_name, std::move(aliases) }); else { // need to update the itemValidator? diff --git a/src/item.cpp b/src/item.cpp index 39b5379..75268e0 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -36,38 +36,70 @@ namespace cif { -const item_handle item_handle::s_null_item; -row_handle s_null_row_handle; - -item_handle::item_handle() noexcept - : m_item_ix(std::numeric_limits::max()) - , m_row_handle(s_null_row_handle) +int item_value::compare(const item_value &b, bool ignore_case) const noexcept { -} + int d = static_cast(m_data.m_type) - static_cast(b.m_data.m_type); -std::string_view item_handle::text() const -{ - if (not m_row_handle.empty()) + if (d == 0) { - auto iv = m_row_handle.m_row->get(m_item_ix); - if (iv != nullptr) - return iv->text(); + switch (m_data.m_type) + { + case cif::item_value_type::BOOLEAN: + d = static_cast(m_data.m_value.m_boolean) - static_cast(b.m_data.m_value.m_boolean); + break; + case cif::item_value_type::INT: + d = m_data.m_value.m_integer - b.m_data.m_value.m_integer; + break; + case cif::item_value_type::FLOAT: + { + auto dp = (m_data.m_value.m_float <=> b.m_data.m_value.m_float); + if (dp == std::partial_ordering::less) + d = -1; + else if (dp == std::partial_ordering::greater) + d = 1; + break; + } + case cif::item_value_type::TEXT: + d = m_data.sv().compare(b.m_data.sv()); + break; + default:; + } } - return {}; + return d; } -void item_handle::assign_value(std::string_view value) -{ - assert(not m_row_handle.empty()); - m_row_handle.assign(m_item_ix, value, true); -} +// void const_item_handle::assign_value(const item_value &value) +// { +// assert(not m_row_handle.empty()); +// m_row_handle.assign(m_item_ix, value, true); +// } -void item_handle::swap(item_handle &b) +std::ostream &operator<<(std::ostream &os, const item_value &v) { - assert(m_item_ix == b.m_item_ix); - // assert(&m_row_handle.m_category == &b.m_row_handle.m_category); - m_row_handle.swap(m_item_ix, b.m_row_handle); + switch (v.type()) + { + case cif::item_value_type::BOOLEAN: + os << std::boolalpha << v.m_data.m_value.m_boolean; + break; + case cif::item_value_type::INT: + os << v.m_data.m_value.m_integer; + break; + case cif::item_value_type::FLOAT: + os << v.m_data.m_value.m_float; + break; + case cif::item_value_type::TEXT: + os << v.m_data.sv(); + break; + case cif::item_value_type::MISSING: + os << '?'; + break; + case cif::item_value_type::INAPPLICABLE: + os << '.'; + break; + } + + return os; } } // namespace cif diff --git a/src/model.cpp b/src/model.cpp index dfe008f..b1824d2 100644 --- a/src/model.cpp +++ b/src/model.cpp @@ -237,7 +237,7 @@ atom residue::create_new_atom(atom_type inType, const std::string &inAtomID, poi { "auth_atom_id", inAtomID }, { "auth_comp_id", m_compound_id }, { "auth_seq_id", m_pdb_seq_num }, - { "occupancy", 1.0f, 2 }, + { "occupancy", 1.0f/* , 2 */ }, { "B_iso_or_equiv", 20.0f }, { "pdbx_PDB_model_num", m_structure->get_model_nr() }, }); @@ -955,8 +955,8 @@ cif::mm::atom sugar::add_atom(row_initializer atom_info) atom_info.set_value({ "auth_asym_id", m_branch->get_asym_id() }); atom_info.set_value({ "auth_comp_id", m_compound_id }); atom_info.set_value({ "auth_seq_id", m_pdb_seq_num }); - atom_info.set_value({ "occupancy", 1.0, 2 }); - atom_info.set_value({ "B_iso_or_equiv", 30.0, 2 }); + atom_info.set_value({ "occupancy", 1.0/* , 2 */ }); + atom_info.set_value({ "B_iso_or_equiv", 30.0/* , 2 */ }); atom_info.set_value({ "pdbx_PDB_model_num", 1 }); auto row = atom_site.emplace(std::move(atom_info)); @@ -1859,11 +1859,7 @@ void structure::swap_atoms(atom a1, atom a2) auto r2 = atomSites.find1(key("id") == a2.id()); for (std::string fld : std::initializer_list{ "label_atom_id", "auth_atom_id", "type_symbol" }) - { - auto l1 = r1[fld]; - auto l2 = r2[fld]; - l1.swap(l2); - } + swap(r1[fld].value(), r2[fld].value()); } catch (const std::exception &ex) { @@ -2280,7 +2276,7 @@ std::string structure::create_non_poly(const std::string &entity_id, std::vector atom.set_value_if_empty({ "auth_seq_id", 1 }); atom.set_value_if_empty({ "pdbx_PDB_model_num", 1 }); atom.set_value_if_empty({ "label_alt_id", "" }); - atom.set_value_if_empty({ "occupancy", 1.0, 2 }); + atom.set_value_if_empty({ "occupancy", 1.0/* , 2 */ }); auto row = atom_site.emplace(atom.begin(), atom.end()); @@ -2378,7 +2374,7 @@ void structure::create_water(row_initializer atom) atom.set_value_if_empty({ "auth_comp_id", "HOH" }); atom.set_value_if_empty({ "pdbx_PDB_model_num", 1 }); atom.set_value_if_empty({ "label_alt_id", "" }); - atom.set_value_if_empty({ "occupancy", 1.0, 2 }); + atom.set_value_if_empty({ "occupancy", 1.0/* , 2 */ }); auto row = atom_site.emplace(atom.begin(), atom.end()); @@ -2449,7 +2445,7 @@ std::string structure::create_link(atom a1, atom a2, const std::string &link_typ { "ptnr2_auth_seq_id", a2.get_auth_seq_id() }, { "ptnr2_symmetry", a2.symmetry() }, - { "pdbx_dist_value", distance(a1.get_location(), a2.get_location()), 3 }, + { "pdbx_dist_value", distance(a1.get_location(), a2.get_location())/* , 3 */ }, { "pdbx_role", role } }); return link_id; diff --git a/src/parser.cpp b/src/parser.cpp index b3e2162..92a42d0 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -276,6 +276,8 @@ sac_parser::CIFToken sac_parser::get_next_token() m_token_buffer.clear(); m_token_value = {}; + bool negative = false; + reserved_words_automaton dag; while (result == CIFToken::UNKNOWN) @@ -314,6 +316,15 @@ sac_parser::CIFToken sac_parser::get_next_token() } else if (dag.move(ch) == reserved_words_automaton::undefined) state = State::Reserved; + else if (ch == '+' or ch == '-') + { + negative = true; + state = State::Numeric_Integer; + } + else if (ch >= '0' and ch <= '9') + state = State::Numeric_Integer; + else if (ch == '.') + state = State::Numeric_Float; else state = State::Value; break; @@ -350,7 +361,7 @@ sac_parser::CIFToken sac_parser::get_next_token() if (not is_non_blank(ch)) { retract(); - result = CIFToken::VALUE; + result = CIFToken::VALUE_UNKNOWN; } else state = State::Value; @@ -396,7 +407,7 @@ sac_parser::CIFToken sac_parser::get_next_token() { assert(m_token_buffer.size() >= 2); m_token_value = std::string_view(m_token_buffer.data() + 1, m_token_buffer.size() - 3); - result = CIFToken::VALUE; + result = CIFToken::VALUE_CHARSTRING; } else if (ch == kEOF) error("unterminated textfield"); @@ -411,7 +422,7 @@ sac_parser::CIFToken sac_parser::get_next_token() { assert(m_token_buffer.size() >= 2); m_token_value = std::string_view(m_token_buffer.data() + 1, m_token_buffer.size() - 3); - result = CIFToken::VALUE; + result = CIFToken::VALUE_TEXTFIELD; } else if (ch == kEOF) error("unterminated textfield"); @@ -432,7 +443,7 @@ sac_parser::CIFToken sac_parser::get_next_token() if (is_white(ch)) { retract(); - result = CIFToken::VALUE; + result = CIFToken::VALUE_CHARSTRING; if (m_token_buffer.size() < 2) error("Invalid quoted string token"); @@ -467,7 +478,7 @@ sac_parser::CIFToken sac_parser::get_next_token() if (not is_non_blank(ch)) { retract(); - result = CIFToken::VALUE; + result = CIFToken::VALUE_CHARSTRING; m_token_value = std::string_view(m_token_buffer.data(), m_token_buffer.size()); } else @@ -508,11 +519,65 @@ sac_parser::CIFToken sac_parser::get_next_token() } break; + case State::Numeric_Integer: + if (ch == '.') + state = State::Numeric_Float; + else if (ch == 'e' or ch == 'E') + state = State::Numeric_Exponent1; + else if (not is_non_blank(ch)) + { + retract(); + result = CIFToken::VALUE_NUMERIC_INTEGER; + } + else if (ch < '0' or ch > '9') + state = State::Value; + break; + + case State::Numeric_Float: + if (not is_non_blank(ch)) + { + retract(); + if (m_token_buffer.size() == 1) + result = CIFToken::VALUE_INAPPLICABLE; + else + result = CIFToken::VALUE_NUMERIC_FLOAT; + } + else if (ch == 'e' or ch == 'E') + state = State::Numeric_Exponent1; + else if (ch < '0' or ch > '9') + state = State::Value; + break; + + case State::Numeric_Exponent1: + if (ch == '+' or ch == '-' or (ch >= '0' and ch <= '9')) + state = State::Numeric_Exponent2; + else + { + if (VERBOSE > 0) + std::cerr << "parsing " << std::string_view{ m_token_buffer.data(), m_token_buffer.size() } << " Invalid floating point value, expected digit or sign character\n"; + state = State::Value; + } + break; + + case State::Numeric_Exponent2: + if (not is_non_blank(ch)) + { + retract(); + result = CIFToken::VALUE_NUMERIC_FLOAT; + } + else if (ch < '0' or ch > '9') + { + if (VERBOSE > 0) + std::cerr << "parsing " << std::string_view{ m_token_buffer.data(), m_token_buffer.size() } << " Invalid floating point value, expected exponent digit\n"; + state = State::Value; + } + break; + case State::Value: if (not is_non_blank(ch)) { retract(); - result = CIFToken::VALUE; + result = CIFToken::VALUE_CHARSTRING; m_token_value = std::string_view(m_token_buffer.data(), m_token_buffer.size()); break; } @@ -525,12 +590,25 @@ sac_parser::CIFToken sac_parser::get_next_token() } } - if (VERBOSE >= 5) + // if (VERBOSE >= 5) + // { + // std::cerr << get_token_name(result); + // if (result != CIFToken::END_OF_FILE) + // std::cerr << " " << std::quoted(m_token_value); + // std::cerr << '\n'; + // } + + if (result == CIFToken::VALUE_NUMERIC_INTEGER) { - std::cerr << get_token_name(result); - if (result != CIFToken::END_OF_FILE) - std::cerr << " " << std::quoted(m_token_value); - std::cerr << '\n'; + auto [ptr, ec] = std::from_chars(m_token_buffer.data(), m_token_buffer.data() + m_token_buffer.size(), m_token_value_int); + if (ec != std::errc{}) + error("Invalid integer value: " + std::make_error_code(ec).message()); + } + else if (result == CIFToken::VALUE_NUMERIC_FLOAT) + { + auto [ptr, ec] = std::from_chars(m_token_buffer.data(), m_token_buffer.data() + m_token_buffer.size(), m_token_value_float); + if (ec != std::errc{}) + error("Invalid integer value: " + std::make_error_code(ec).message()); } return result; @@ -785,7 +863,10 @@ void sac_parser::parse_global() while (m_lookahead == CIFToken::ITEM_NAME) { match(CIFToken::ITEM_NAME); - match(CIFToken::VALUE); + if (m_lookahead >= CIFToken::VALUE_INAPPLICABLE) + match(m_lookahead); + else + match(CIFToken::VALUE_CHARSTRING); } } @@ -824,14 +905,38 @@ void sac_parser::parse_datablock() match(CIFToken::ITEM_NAME); } - while (m_lookahead == CIFToken::VALUE) + while (m_lookahead >= CIFToken::VALUE_INAPPLICABLE) { produce_row(); for (auto item_name : item_names) { - produce_item(cat, item_name, m_token_value); - match(CIFToken::VALUE); + switch (m_lookahead) + { + case CIFToken::VALUE_INAPPLICABLE: + produce_item(cat, item_name, nullptr); + match(m_lookahead); + break; + case CIFToken::VALUE_UNKNOWN: + produce_item(cat, item_name, std::optional{}); + match(m_lookahead); + break; + case CIFToken::VALUE_NUMERIC_INTEGER: + produce_item(cat, item_name, m_token_value_int); + match(m_lookahead); + break; + case CIFToken::VALUE_NUMERIC_FLOAT: + produce_item(cat, item_name, m_token_value_float); + match(m_lookahead); + break; + case CIFToken::VALUE_CHARSTRING: + case CIFToken::VALUE_TEXTFIELD: + produce_item(cat, item_name, m_token_value); + match(m_lookahead); + break; + default:; + match(CIFToken::VALUE_CHARSTRING); + } } } @@ -853,9 +958,33 @@ void sac_parser::parse_datablock() match(CIFToken::ITEM_NAME); - produce_item(cat, itemName, m_token_value); + switch (m_lookahead) + { + case CIFToken::VALUE_INAPPLICABLE: + produce_item(cat, itemName, nullptr); + match(CIFToken::VALUE_INAPPLICABLE); + break; + case CIFToken::VALUE_UNKNOWN: + produce_item(cat, itemName, item_value{ std::optional{} }); + match(CIFToken::VALUE_UNKNOWN); + break; + case CIFToken::VALUE_NUMERIC_INTEGER: + produce_item(cat, itemName, m_token_value_int); + match(CIFToken::VALUE_NUMERIC_INTEGER); + break; + case CIFToken::VALUE_NUMERIC_FLOAT: + produce_item(cat, itemName, m_token_value_float); + match(CIFToken::VALUE_NUMERIC_FLOAT); + break; + case CIFToken::VALUE_CHARSTRING: + case CIFToken::VALUE_TEXTFIELD: + produce_item(cat, itemName, m_token_value); + match(m_lookahead); + break; + default: + match(CIFToken::VALUE_CHARSTRING); + } - match(CIFToken::VALUE); break; } diff --git a/src/pdb/cif2pdb.cpp b/src/pdb/cif2pdb.cpp index 7860690..3740ad4 100644 --- a/src/pdb/cif2pdb.cpp +++ b/src/pdb/cif2pdb.cpp @@ -652,9 +652,9 @@ class FBase mRow = r.front(); } - [[nodiscard]] std::string_view text() const + [[nodiscard]] std::string text() const { - return mRow.empty() or mRow[mField].empty() ? "" : mRow[mField].text(); + return mRow.empty() or mRow[mField].empty() ? "" : mRow[mField].str(); } row_handle mRow; diff --git a/src/pdb/pdb2cif.cpp b/src/pdb/pdb2cif.cpp index 9ce26b0..7b23358 100644 --- a/src/pdb/pdb2cif.cpp +++ b/src/pdb/pdb2cif.cpp @@ -4566,7 +4566,7 @@ void PDBFileParser::ConstructEntities() { "id", cc }, { "name", name }, { "formula", formula }, - { "formula_weight", formulaWeight, 3 }, + { "formula_weight", formulaWeight/* , 3 */ }, { "mon_nstd_flag", nstd }, { "type", type } }); diff --git a/src/pdb/pdb2cif_remark_3.cpp b/src/pdb/pdb2cif_remark_3.cpp index 36373f9..799fc24 100644 --- a/src/pdb/pdb2cif_remark_3.cpp +++ b/src/pdb/pdb2cif_remark_3.cpp @@ -1534,7 +1534,7 @@ bool Remark3Parser::parse(const std::string &expMethod, PDBRecord *r, cif::datab continue; for (auto &iv : cv->m_item_validators) - r2[iv.m_item_name] = r1[iv.m_item_name].text(); + r2[iv.m_item_name] = r1[iv.m_item_name].str(); } } else diff --git a/src/pdb/reconstruct.cpp b/src/pdb/reconstruct.cpp index ede0954..caeee07 100644 --- a/src/pdb/reconstruct.cpp +++ b/src/pdb/reconstruct.cpp @@ -26,7 +26,8 @@ #include "cif++.hpp" #include "cif++/compound.hpp" -#include "cif++/cql.hpp" +// #include "cif++/cql.hpp" +#include "cif++/item.hpp" #include "cif++/point.hpp" #include "cif++/row.hpp" @@ -187,7 +188,7 @@ void checkEntities(datablock &db) } if (formula_weight > 0) - entity.assign({ { "formula_weight", formula_weight, 3 } }); + entity.assign({ { "formula_weight", formula_weight/* , 3 */ } }); } } @@ -454,10 +455,10 @@ void checkChemCompRecords(datablock &db) for (auto chem_comp_entry : chem_comp) { - auto compound = cf.create(chem_comp_entry["id"].text()); + auto compound = cf.create(chem_comp_entry["id"].str()); if (not compound) - std::cerr << "Unknown compound: " << chem_comp_entry["id"].text() << '\n'; + std::cerr << "Unknown compound: " << chem_comp_entry["id"].str() << '\n'; else { std::vector items; @@ -544,7 +545,7 @@ void checkAtomRecords(datablock &db) if (row["type_symbol"].empty()) throw std::runtime_error("Missing type symbol in atom_site record"); - std::string symbol{ row["type_symbol"].text() }; + std::string symbol{ row["type_symbol"].str() }; if (atom_type.count("symbol"_key == symbol) == 0) atom_type.emplace({ { "symbol", symbol } }); @@ -617,19 +618,19 @@ void checkAtomRecords(datablock &db) row["label_seq_id"] = std::to_string(seq_id); if (row["label_asym_id"].empty()) - row["label_asym_id"] = row["auth_asym_id"].text(); + row["label_asym_id"] = row["auth_asym_id"].value(); else if (row["auth_asym_id"].empty()) - row["auth_asym_id"] = row["label_asym_id"].text(); + row["auth_asym_id"] = row["label_asym_id"].value(); if (row["label_comp_id"].empty()) - row["label_comp_id"] = row["auth_comp_id"].text(); + row["label_comp_id"] = row["auth_comp_id"].value(); else if (row["auth_comp_id"].empty()) - row["auth_comp_id"] = row["label_comp_id"].text(); + row["auth_comp_id"] = row["label_comp_id"].value(); if (row["label_atom_id"].empty()) - row["label_atom_id"] = row["auth_atom_id"].text(); + row["label_atom_id"] = row["auth_atom_id"].value(); else if (row["auth_atom_id"].empty()) - row["auth_atom_id"] = row["label_atom_id"].text(); + row["auth_atom_id"] = row["label_atom_id"].value(); // Rewrite the coordinates and other items that look better in a fixed format // Be careful not to nuke invalidly formatted data here @@ -648,14 +649,14 @@ void checkAtomRecords(datablock &db) if (auto [ptr, ec] = cif::from_chars(s.data(), s.data() + s.length(), v); ec != std::errc{}) continue; - if (s.length() < prec + 1UL or s[s.length() - prec - 1] != '.') +/* if (s.length() < prec + 1UL or s[s.length() - prec - 1] != '.') { char b[12]; if (auto [ptr, ec] = std::to_chars(b, b + sizeof(b), v, std::chars_format::fixed, prec); ec == std::errc{}) row.assign(item_name, { b, static_cast(ptr - b) }, false, false); } - } + */ } } // auto *cv = atom_site.get_cat_validator(); @@ -714,24 +715,24 @@ void checkAtomAnisotropRecords(datablock &db) // this happens sometimes (Phenix): if (row["type_symbol"].empty()) - row["type_symbol"] = parent["type_symbol"].text(); - else if (row["type_symbol"].text() != parent["type_symbol"].text()) + row["type_symbol"] = parent["type_symbol"].value(); + else if (row["type_symbol"].value() != parent["type_symbol"].value()) { if (cif::VERBOSE and std::exchange(warnReplaceTypeSymbol, false)) std::clog << "Replacing type_symbol in atom_site_anisotrop record(s)\n"; - row["type_symbol"] = parent["type_symbol"].text(); + row["type_symbol"] = parent["type_symbol"].value(); } if (row["pdbx_auth_alt_id"].empty() and not parent["pdbx_auth_alt_id"].empty()) - row["pdbx_auth_alt_id"] = parent["pdbx_auth_alt_id"].text(); + row["pdbx_auth_alt_id"] = parent["pdbx_auth_alt_id"].value(); if (row["pdbx_label_seq_id"].empty() and not parent["label_seq_id"].empty()) - row["pdbx_label_seq_id"] = parent["label_seq_id"].text(); + row["pdbx_label_seq_id"] = parent["label_seq_id"].value(); if (row["pdbx_label_asym_id"].empty() and not parent["label_asym_id"].empty()) - row["pdbx_label_asym_id"] = parent["label_asym_id"].text(); + row["pdbx_label_asym_id"] = parent["label_asym_id"].value(); if (row["pdbx_label_atom_id"].empty() and not parent["label_atom_id"].empty()) - row["pdbx_label_atom_id"] = parent["label_atom_id"].text(); + row["pdbx_label_atom_id"] = parent["label_atom_id"].value(); if (row["pdbx_label_comp_id"].empty() and not parent["label_comp_id"].empty()) - row["pdbx_label_comp_id"] = parent["label_comp_id"].text(); + row["pdbx_label_comp_id"] = parent["label_comp_id"].value(); } if (not to_be_deleted.empty()) @@ -1130,7 +1131,7 @@ void createPdbxPolySeqScheme(datablock &db) for (auto col : { "label_asym_id", "label_entity_id", "label_seq_id", "label_comp_id", "auth_seq_id", "auth_comp_id", "pdbx_PDB_ins_code"}) atom_site.add_item(col); - cql::connection conn(db); +/* cql::connection conn(db); cql::transaction tx(conn); for (auto &&[asym_id, entity_id, seq_id, comp_id, auth_seq_id, auth_comp_id, pdb_ins_code] : tx.stream, std::string, std::string, std::string, std::optional>( @@ -1165,7 +1166,7 @@ void createPdbxPolySeqScheme(datablock &db) last_asym_id = asym_id; last_seq_id = seq_id; } - + */ // // select distinct A.entity_id, A.id, B.mon_id, B.num, B.hetero, C.auth_seq_id, C.auth_comp_id, C.pdbx_PDB_ins_code from struct_asym A, entity_poly_seq B, atom_site C where A.entity_id = B.entity_id and C.label_asym_id = A.id and C.label_seq_id = B.num order by A.entity_id, B.num; // // select distinct label_entity_id, label_asym_id, label_comp_id, label_seq_id, auth_asym_id, auth_seq_id, auth_comp_id from atom_site order by CAST(label_entity_id AS INT), label_asym_id, CAST(label_seq_id AS INT); @@ -1658,14 +1659,13 @@ bool reconstruct_pdbx(file &file, const validator &validator) for (auto row : cat) { std::error_code ec; - std::string_view value = row[ix].text(); - if (not iv->validate_value(value, ec)) + if (not iv->validate_value(row[ix].value(), ec)) { if (cif::VERBOSE > 0) - std::clog << "Replacing value (" << std::quoted(value) << ") for item " << item_name << " in category " << cat.name() << " since it does not validate\n"; + std::clog << "Replacing value (" << std::quoted(row[ix].str()) << ") for item " << item_name << " in category " << cat.name() << " since it does not validate\n"; - row[ix] = "?"; + row[ix] = item_value{ cif::item_value_type::INAPPLICABLE }; } } } diff --git a/src/pdb/validate-pdbx.cpp b/src/pdb/validate-pdbx.cpp index f496326..45ea2bc 100644 --- a/src/pdb/validate-pdbx.cpp +++ b/src/pdb/validate-pdbx.cpp @@ -55,7 +55,7 @@ condition get_parents_condition(const validator &validator, row_handle rh, const if (childValue.empty()) continue; - cond = std::move(cond) and key(link->m_parent_keys[ix]) == childValue.text(); + cond = std::move(cond) and key(link->m_parent_keys[ix]) == childValue.value(); } result = std::move(result) or std::move(cond); @@ -145,7 +145,7 @@ bool is_valid_pdbx_file(const file &file, const validator &validator, std::error if (p.size() != 1) { if (VERBOSE > 0) - std::clog << "In atom_site record: " << r["id"].text() << '\n'; + std::clog << "In atom_site record: " << r["id"].str() << '\n'; throw std::runtime_error("For each monomer in atom_site there should be exactly one pdbx_poly_seq_scheme record"); } } diff --git a/src/row.cpp b/src/row.cpp index 251cbb6..7af331c 100644 --- a/src/row.cpp +++ b/src/row.cpp @@ -40,37 +40,25 @@ namespace cif { -item_value s_null_item; +// item_value &row_handle::operator[](uint16_t item_ix) +// { +// return empty() or item_ix >= m_row->size() ? s_null_item : m_row->operator[](item_ix); +// } -item_value &row_handle::operator[](uint16_t item_ix) -{ - return empty() or item_ix >= m_row->size() ? s_null_item : m_row->operator[](item_ix); -} +// const item_value &row_handle::operator[](uint16_t item_ix) const +// { +// return empty() or item_ix >= m_row->size() ? s_null_item : m_row->operator[](item_ix); +// } -const item_value &row_handle::operator[](uint16_t item_ix) const -{ - return empty() or item_ix >= m_row->size() ? s_null_item : m_row->operator[](item_ix); -} +// item_value &row_handle::operator[](std::string_view item_name) +// { +// return operator[](get_item_ix(item_name)); +// } -item_value &row_handle::operator[](std::string_view item_name) -{ - return operator[](get_item_ix(item_name)); -} - -const item_value &row_handle::operator[](std::string_view item_name) const -{ - return operator[](get_item_ix(item_name)); -} - -// -------------------------------------------------------------------- - -void row_handle::assign(uint16_t item, item_value value, bool updateLinked, bool validate) -{ - if (not m_category) - throw std::runtime_error("uninitialized row"); - - m_category->update_value(m_row, item, std::move(value), updateLinked, validate); -} +// const item_value &row_handle::operator[](std::string_view item_name) const +// { +// return operator[](get_item_ix(item_name)); +// } uint16_t row_handle::get_item_ix(std::string_view name) const { @@ -88,6 +76,32 @@ std::string_view row_handle::get_item_name(uint16_t ix) const return m_category->get_item_name(ix); } +uint16_t const_row_handle::get_item_ix(std::string_view name) const +{ + if (not m_category) + throw std::runtime_error("uninitialized row"); + + return m_category->get_item_ix(name); +} + +std::string_view const_row_handle::get_item_name(uint16_t ix) const +{ + if (not m_category) + throw std::runtime_error("uninitialized row"); + + return m_category->get_item_name(ix); +} + +// -------------------------------------------------------------------- + +void row_handle::assign(uint16_t item, item_value value, bool updateLinked, bool validate) +{ + if (not m_category) + throw std::runtime_error("uninitialized row"); + + m_category->update_value(m_row, item, std::move(value), updateLinked, validate); +} + uint16_t row_handle::add_item(std::string_view name) { if (not m_category) @@ -96,24 +110,16 @@ uint16_t row_handle::add_item(std::string_view name) return m_category->add_item(name); } -void row_handle::swap(uint16_t item, row_handle &b) -{ - if (not m_category) - throw std::runtime_error("uninitialized row"); - - m_category->swap_item(item, *this, b); -} - // -------------------------------------------------------------------- -row_initializer::row_initializer(row_handle rh) +row_initializer::row_initializer(const_row_handle rh) { if (not rh.m_category) throw std::runtime_error("uninitialized row"); assert(rh.m_row); - row *r = rh.get_row(); + auto r = rh.get_row(); auto &cat = *rh.m_category; for (uint16_t ix = 0; ix < r->size(); ++ix) diff --git a/src/validate.cpp b/src/validate.cpp index 9eb9117..53c2e54 100644 --- a/src/validate.cpp +++ b/src/validate.cpp @@ -257,22 +257,22 @@ int type_validator::compare(std::string_view a, std::string_view b) const // -------------------------------------------------------------------- -void item_validator::operator()(std::string_view value) const +void item_validator::validate_value(const item_value &value) const { std::error_code ec; if (not validate_value(value, ec)) - throw std::system_error(ec, std::format("'{}' is not a valid value for {}", value, m_item_name)); + throw std::system_error(ec, std::format("'{}' is not a valid value for {}", value.str(), m_item_name)); } -bool item_validator::validate_value(std::string_view value, std::error_code &ec) const noexcept +bool item_validator::validate_value(const item_value &value, std::error_code &ec) const noexcept { ec.clear(); - if (not value.empty() and value != "?" and value != ".") + if (not value.empty()) { - if (m_type != nullptr and not m_type->m_rx->match(value)) + if (m_type != nullptr and not m_type->m_rx->match(value.str())) ec = make_error_code(validation_error::value_does_not_match_rx); - else if (not m_enums.empty() and m_enums.count(std::string{ value }) == 0) + else if (not m_enums.empty() and m_enums.count(value.str()) == 0) ec = make_error_code(validation_error::value_is_not_in_enumeration_list); } diff --git a/test/unit-v2-test.cpp b/test/unit-v2-test.cpp index 898398d..ceebb7a 100644 --- a/test/unit-v2-test.cpp +++ b/test/unit-v2-test.cpp @@ -28,25 +28,32 @@ #include "test-main.hpp" // #include +#include #include +#include +#include +#include +#include #include +#include +#include #include -// // -------------------------------------------------------------------- +// -------------------------------------------------------------------- -// cif::file operator""_cf(const char *text, std::size_t length) -// { -// struct membuf : public std::streambuf -// { -// membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } buffer(const_cast(text), length); +cif::file operator""_cf(const char *text, std::size_t length) +{ + struct membuf : public std::streambuf + { + membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } buffer(const_cast(text), length); -// std::istream is(&buffer); -// return cif::file(is); -// } + std::istream is(&buffer); + return cif::file(is); +} // -------------------------------------------------------------------- @@ -168,7 +175,7 @@ TEST_CASE("cc_2") } } -TEST_CASE("item_1") +TEST_CASE("item_0") { cif::item i1("v1", "tekst"); cif::item i2("v2", 2); @@ -183,11 +190,17 @@ TEST_CASE("item_1") CHECK(i2.value().get() == 2); CHECK(i3.value().get() == 3.0); CHECK(i4.value().get() == true); - CHECK(i5.value().is_empty()); + CHECK(i5.value().is_null()); + CHECK(i5.value().is_missing()); + CHECK(i5.value().empty()); - i1.value() = false; - CHECK(i1.value().type() == cif::item_value_type::BOOLEAN); - CHECK(i1.value().get() == false); + i2.value() = false; + CHECK(i2.value().type() == cif::item_value_type::BOOLEAN); + CHECK(i2.value().get() == false); + + cif::item i6 = std::move(i1); + CHECK(i6.value().get() == "tekst"); + CHECK(i1.value().is_null()); } TEST_CASE("row_1") @@ -201,7 +214,6 @@ TEST_CASE("row_1") cat.add_item("een"); cat.add_item("twee"); - cif::row_handle rh(cat, r); @@ -209,11 +221,9 @@ TEST_CASE("row_1") CHECK(a == 1); CHECK(b == "twee"); - rh.assign("twee", 3.0, false); CHECK(rh[1].type() == cif::item_value_type::FLOAT); CHECK(rh[1].get() == 3.0); - } TEST_CASE("cc_3") @@ -231,237 +241,236 @@ TEST_CASE("cc_3") auto row = c.front(); CHECK(row["f-1"].get() == 1); CHECK(row["f-2"].get() == -1); - CHECK(row["f-3"].get() == 1); - // CHECK_THROWS_AS(row["f-4"].as(), std::exception); - // CHECK_THROWS_AS(row["f-5"].as(), std::exception); - // CHECK_THROWS_AS(row["f-6"].as(), std::exception); - CHECK(row["f-4"].get() == 0); - CHECK(row["f-5"].get() == 0); - CHECK(row["f-6"].get() == 0); + CHECK_FALSE(row["f-3"].is_number()); + CHECK_FALSE(row["f-4"].is_number()); + CHECK_FALSE(row["f-5"].is_number()); + CHECK_FALSE(row["f-6"].is_number()); } -// TEST_CASE("item_1") -// { -// using namespace cif; +TEST_CASE("item_1") +{ + using namespace cif; -// item i1("1", "1"); -// item i2("2", 2.0f); -// item i3("3", '3'); + item i1("1", "1"); + item i2("2", 2.0f); + item i3("3", '3'); -// item ci1(i1); -// item ci2(i2); -// item ci3(i3); + item ci1(i1); + item ci2(i2); + item ci3(i3); -// CHECK(i1.value() == ci1.value()); -// CHECK(i2.value() == ci2.value()); -// CHECK(i3.value() == ci3.value()); + CHECK(i1.value() == ci1.value()); + CHECK(i2.value() == ci2.value()); + CHECK(i3.value() == ci3.value()); -// item mi1(std::move(ci1)); -// item mi2(std::move(ci2)); -// item mi3(std::move(ci3)); + item mi1(std::move(ci1)); + item mi2(std::move(ci2)); + item mi3(std::move(ci3)); -// CHECK(i1.value() == mi1.value()); -// CHECK(i2.value() == mi2.value()); -// CHECK(i3.value() == mi3.value()); + CHECK(i1.value() == mi1.value()); + CHECK(i2.value() == mi2.value()); + CHECK(i3.value() == mi3.value()); -// CHECK(ci1.empty()); -// CHECK(ci2.empty()); -// CHECK(ci3.empty()); -// } + CHECK(ci1.empty()); + CHECK(ci2.empty()); + CHECK(ci3.empty()); +} -// TEST_CASE("item_2") -// { -// using namespace cif; +TEST_CASE("item_2") +{ + using namespace cif; -// cif::item i0("test1"); -// CHECK(i0.value() == "."); + cif::item i0("test1"); + CHECK(i0.value().empty()); + CHECK(i0.value().type() == cif::item_value_type::INAPPLICABLE); -// cif::item i1("test1", std:: optional()); -// CHECK(i1.value() == "?"); + cif::item i1("test1", std::optional()); + CHECK(i0.value().empty()); + // CHECK(i1.value() == "?"); -// cif::item i2("test1", std::make_optional(1.f)); -// CHECK(i2.value() == "1"); + cif::item i2("test1", std::make_optional(1.f)); + // CHECK(i2.value() == "1"); -// cif::item i3("test1", std::optional(), 2); -// CHECK(i3.value() == "?"); + // cif::item i3("test1", std::optional(), 2); + // CHECK(i3.value() == "?"); -// cif::item i4("test1", std::make_optional(1.f), 2); -// CHECK(i4.value() == "1.00"); -// } + // cif::item i4("test1", std::make_optional(1.f), 2); + // CHECK(i4.value() == "1.00"); +} -// // -------------------------------------------------------------------- +// -------------------------------------------------------------------- -// TEST_CASE("r_1") -// { -// cif::category c("foo"); -// c.emplace({ -// { "f-1", 1 }, -// { "f-2", "two" }, -// { "f-3", 3.0f, 3 }, -// }); +TEST_CASE("r_1") +{ + cif::category c("foo"); + c.emplace({ + { "f-1", 1 }, + { "f-2", "two" }, + { "f-3", 3.0f/* , 3 */ }, + }); -// auto row = c.front(); -// CHECK(row["f-1"].compare(1) == 0); -// CHECK(row["f-2"].compare("two") == 0); -// CHECK(row["f-3"].compare(3.0f) == 0); // This fails when running in valgrind... sigh + auto row = c.front(); + // CHECK(row["f-1"].compare(1) == 0); + // CHECK(row["f-2"].compare("two") == 0); + // CHECK(row["f-3"].compare(3.0f) == 0); // This fails when running in valgrind... sigh -// const auto &[f1, f2, f3] = row.get("f-1", "f-2", "f-3"); + const auto &[f1, f2, f3] = row.get("f-1", "f-2", "f-3"); -// CHECK(f1 == 1); -// CHECK(f2 == "two"); -// CHECK(f3 == 3.0f); // This fails when running in valgrind... sigh + CHECK(f1 == 1); + CHECK(f2 == "two"); + CHECK(f3 == 3.0f); // This fails when running in valgrind... sigh -// CHECK(row.get("f-1") == 1); -// CHECK(row.get("f-2") == "two"); -// CHECK(row.get("f-3") == 3.0f); + CHECK(row.get("f-1") == 1); + CHECK(row.get("f-2") == "two"); + CHECK(row.get("f-3") == 3.0f); -// int f_1; -// std::string f_2; -// float f_3; + int f_1; + std::string f_2; + float f_3; -// cif::tie(f_1, f_2, f_3) = row.get("f-1", "f-2", "f-3"); + cif::tie(f_1, f_2, f_3) = row.get("f-1", "f-2", "f-3"); -// CHECK(f_1 == 1); -// CHECK(f_2 == "two"); -// CHECK(f_3 == 3.0f); // This fails when running in valgrind... sigh -// } + CHECK(f_1 == 1); + CHECK(f_2 == "two"); + CHECK(f_3 == 3.0f); // This fails when running in valgrind... sigh +} -// TEST_CASE("r_2") -// { -// cif::category c("foo"); +TEST_CASE("r_2") +{ + cif::category c("foo"); -// for (std::size_t i = 1; i < 256; ++i) -// { -// c.emplace({ { "id", i }, -// { "txt", std::string(i, 'x') } }); -// } -// } + for (std::size_t i = 1; i < 256; ++i) + { + c.emplace({ { "id", i }, + { "txt", std::string(i, 'x') } }); + } +} -// TEST_CASE("c_1") -// { -// cif::category c("foo"); +TEST_CASE("c_1") +{ + cif::category c("foo"); -// c.emplace({ { "id", 1 }, { "s", "aap" } }); -// c.emplace({ { "id", 2 }, { "s", "noot" } }); -// c.emplace({ { "id", 3 }, { "s", "mies" } }); + c.emplace({ { "id", 1 }, { "s", "aap" } }); + c.emplace({ { "id", 2 }, { "s", "noot" } }); + c.emplace({ { "id", 3 }, { "s", "mies" } }); -// int n = 1; + int n = 1; -// const char *ts[] = { "aap", "noot", "mies" }; + const char *ts[] = { "aap", "noot", "mies" }; -// for (auto r : c) -// { -// CHECK(r["id"].as() == n); -// CHECK(r["s"].compare(ts[n - 1]) == 0); -// ++n; -// } + for (auto r : c) + { + CHECK(r["id"].get() == n); + CHECK(r["s"].compare(ts[n - 1]) == 0); + ++n; + } -// n = 1; + n = 1; -// for (auto r : c) -// { -// int i; -// std::string s; + for (auto r : c) + { + int i; + std::string s; -// cif::tie(i, s) = r.get("id", "s"); + cif::tie(i, s) = r.get("id", "s"); -// CHECK(i == n); -// CHECK(s.compare(ts[n - 1]) == 0); -// ++n; -// } + CHECK(i == n); + CHECK(s.compare(ts[n - 1]) == 0); + ++n; + } -// n = 1; + n = 1; -// for (const auto &[i, s] : c.rows("id", "s")) -// { -// CHECK(i == n); -// CHECK(s.compare(ts[n - 1]) == 0); -// ++n; -// } -// } + for (const auto &[i, s] : c.rows("id", "s")) + { + CHECK(i == n); + CHECK(s.compare(ts[n - 1]) == 0); + ++n; + } +} -// TEST_CASE("c_2") -// { -// std::tuple D[] = { -// { 1, "aap" }, -// { 2, "noot" }, -// { 3, "mies" } -// }; +TEST_CASE("c_2") +{ + std::tuple D[] = { + { 1, "aap" }, + { 2, "noot" }, + { 3, "mies" } + }; -// cif::category c("foo"); + cif::category c("foo"); -// for (const auto &[id, s] : D) -// c.emplace({ { "id", id }, { "s", s } }); + for (const auto &[id, s] : D) + c.emplace({ { "id", id }, { "s", s } }); -// CHECK(not c.empty()); -// CHECK(c.size() == 3); + CHECK(not c.empty()); + CHECK(c.size() == 3); -// cif::category c2(c); + cif::category c2(c); -// CHECK(not c2.empty()); -// CHECK(c2.size() == 3); + CHECK(not c2.empty()); + CHECK(c2.size() == 3); -// cif::category c3(std::move(c)); + cif::category c3(std::move(c)); -// CHECK(not c3.empty()); -// CHECK(c3.size() == 3); + CHECK(not c3.empty()); + CHECK(c3.size() == 3); -// CHECK(c.empty()); -// CHECK(c.size() == 0); + CHECK(c.empty()); + CHECK(c.size() == 0); -// c = c3; + c = c3; -// CHECK(not c.empty()); -// CHECK(c.size() == 3); + CHECK(not c.empty()); + CHECK(c.size() == 3); -// c = std::move(c2); + c = std::move(c2); -// CHECK(not c.empty()); -// CHECK(c.size() == 3); -// } + CHECK(not c.empty()); + CHECK(c.size() == 3); +} -// TEST_CASE("c_3") -// { -// std::tuple D[] = { -// { 1, "aap" }, -// { 2, "noot" }, -// { 3, "mies" } -// }; +TEST_CASE("c_3") +{ + std::tuple D[] = { + { 1, "aap" }, + { 2, "noot" }, + { 3, "mies" } + }; -// cif::category c("foo"); + cif::category c("foo"); -// for (const auto &[id, s] : D) -// c.emplace({ { "id", id }, { "s", s } }); + for (const auto &[id, s] : D) + c.emplace({ { "id", id }, { "s", s } }); -// cif::category c2("bar"); + cif::category c2("bar"); -// for (auto r : c) -// c2.emplace(r); + for (auto r : c) + c2.emplace(r); -// // CHECK(c == c2); -// } + // CHECK(c == c2); +} -// TEST_CASE("ci_1") -// { -// cif::category c("foo"); +TEST_CASE("ci_1") +{ + cif::category c("foo"); -// c.emplace({ { "id", 1 }, { "s", "aap" } }); -// c.emplace({ { "id", 2 }, { "s", "noot" } }); -// c.emplace({ { "id", 3 }, { "s", "mies" } }); + c.emplace({ { "id", 1 }, { "s", "aap" } }); + c.emplace({ { "id", 2 }, { "s", "noot" } }); + c.emplace({ { "id", 3 }, { "s", "mies" } }); -// cif::category::iterator i1 = c.begin(); -// cif::category::const_iterator i2 = c.cbegin(); -// cif::category::const_iterator i3 = c.begin(); + cif::category::iterator i1 = c.begin(); + cif::category::const_iterator i2 = c.cbegin(); + cif::category::const_iterator i3 = c.begin(); -// cif::category::const_iterator i4 = i2; -// cif::category::const_iterator i5 = i1; + cif::category::const_iterator i4 = i2; + cif::category::const_iterator i5 = i1; -// CHECK(i1 == i2); -// CHECK(i1 == i3); -// CHECK(i1 == i4); -// CHECK(i1 == i5); -// } + CHECK(i1 == i2); + CHECK(i1 == i3); + CHECK(i1 == i4); + CHECK(i1 == i5); +} // TEST_CASE("os_1") // { @@ -494,194 +503,203 @@ TEST_CASE("cc_3") // } // } -// // -------------------------------------------------------------------- +// -------------------------------------------------------------------- -// TEST_CASE("get_1") -// { -// auto f = R"(data_TEST -// # -// loop_ -// _test.id -// _test.name -// 1 aap -// 2 noot -// 3 mies -// 4 ? -// 5 . -// )"_cf; +TEST_CASE("get_1") +{ + auto f = R"(data_TEST +# +loop_ +_test.id +_test.name +1 aap +2 noot +3 mies +4 ? +5 . + )"_cf; -// for (auto r : f.front()["test"]) -// { -// int id; -// std::optional name; + REQUIRE(f.size() == 1); + REQUIRE(f.contains("TEST")); + REQUIRE(f.front().size() == 1); + REQUIRE(f.front().contains("test")); + REQUIRE(f.front()["test"].size() == 5); + REQUIRE(f.front()["test"].front().size() == 2); -// cif::tie(id, name) = r.get("id", "name"); + for (auto r : f.front()["test"]) + { + int id; + std::optional name; -// switch (id) -// { -// case 1: CHECK(*name == "aap"); break; -// case 2: CHECK(*name == "noot"); break; -// case 3: CHECK(*name == "mies"); break; -// default: CHECK(name.has_value() == false); -// } -// } -// } + cif::tie(id, name) = r.get("id", "name"); -// // -------------------------------------------------------------------- + switch (id) + { + case 1: CHECK(*name == "aap"); break; + case 2: CHECK(*name == "noot"); break; + case 3: CHECK(*name == "mies"); break; + case 4: + case 5: CHECK_FALSE(name.has_value());break; + default: CHECK(false); + } + } +} -// TEST_CASE("f_1") -// { -// // using namespace mmcif; +// -------------------------------------------------------------------- -// auto f = R"(data_TEST -// # -// loop_ -// _test.id -// _test.name -// 1 aap -// 2 noot -// 3 mies -// )"_cf; +TEST_CASE("f_1") +{ + // using namespace mmcif; -// CHECK(f.empty() == false); -// CHECK(f.size() == 1); + auto f = R"(data_TEST +# +loop_ +_test.id +_test.name +1 aap +2 noot +3 mies + )"_cf; -// auto &db = f.front(); + CHECK(f.empty() == false); + CHECK(f.size() == 1); -// CHECK(db.name() == "TEST"); + auto &db = f.front(); -// auto &test = db["test"]; -// CHECK(test.size() == 3); + CHECK(db.name() == "TEST"); -// const char *ts[] = { "aap", "noot", "mies" }; + auto &test = db["test"]; + CHECK(test.size() == 3); -// int n = 1; -// for (const auto &[i, s] : test.rows("id", "name")) -// { -// CHECK(i == n); -// CHECK(s.compare(ts[n - 1]) == 0); -// ++n; -// } + const char *ts[] = { "aap", "noot", "mies" }; -// auto n2 = test.erase(cif::key("id") == 1, [](cif::row_handle r) -// { -// CHECK(r["id"].as() == 1); -// CHECK(r["name"].as() == "aap"); }); + int n = 1; + for (const auto &[i, s] : test.rows("id", "name")) + { + CHECK(i == n); + CHECK(s.compare(ts[n - 1]) == 0); + ++n; + } -// CHECK(n2 == 1); + auto n2 = test.erase(cif::key("id") == 1, [](cif::row_handle r) + { + CHECK(r["id"].get() == 1); + CHECK(r["name"].get() == "aap"); }); -// // for (auto r: test) -// // test.erase(r); + CHECK(n2 == 1); -// test.clear(); -// CHECK(test.empty()); + // for (auto r: test) + // test.erase(r); -// // fill again. + test.clear(); + CHECK(test.empty()); -// test.emplace({ { "id", "1" }, { "name", "aap" } }); -// test.emplace({ { "id", "2" }, { "name", "noot" } }); -// test.emplace({ { "id", "3" }, { "name", "mies" } }); + // fill again. -// n = 1; -// for (const auto &[i, s] : test.rows("id", "name")) -// { -// CHECK(i == n); -// CHECK(s.compare(ts[n - 1]) == 0); -// ++n; -// } -// } + test.emplace({ { "id", "1" }, { "name", "aap" } }); + test.emplace({ { "id", "2" }, { "name", "noot" } }); + test.emplace({ { "id", "3" }, { "name", "mies" } }); -// // -------------------------------------------------------------------- + n = 1; + for (const auto &[i, s] : test.rows("id", "name")) + { + CHECK(i == n); + CHECK(s.compare(ts[n - 1]) == 0); + ++n; + } +} -// TEST_CASE("ut2") -// { -// // using namespace mmcif; +// -------------------------------------------------------------------- -// auto f = R"(data_TEST -// # -// loop_ -// _test.id -// _test.name -// _test.value -// 1 aap 1.0 -// 2 noot 1.1 -// 3 mies 1.2 -// )"_cf; +TEST_CASE("ut2") +{ + // using namespace mmcif; -// auto &db = f.front(); + auto f = R"(data_TEST +# +loop_ +_test.id +_test.name +_test.value +1 aap 1.0 +2 noot 1.1 +3 mies 1.2 + )"_cf; -// CHECK(db.name() == "TEST"); + auto &db = f.front(); -// auto &test = db["test"]; -// CHECK(test.size() == 3); + CHECK(db.name() == "TEST"); -// int n = 0; -// for (auto r : test.find(cif::key("name") == "aap")) -// { -// CHECK(++n == 1); -// CHECK(r["id"].as() == 1); -// CHECK(r["name"].as() == "aap"); -// CHECK(r["value"].as() == 1.0f); -// } + auto &test = db["test"]; + CHECK(test.size() == 3); -// auto t = test.find(cif::key("id") == 1); -// CHECK(not t.empty()); -// CHECK(t.front()["name"].as() == "aap"); + int n = 0; + for (auto r : test.find(cif::key("name") == "aap")) + { + CHECK(++n == 1); + CHECK(r["id"].get() == 1); + CHECK(r["name"].get() == "aap"); + CHECK(r["value"].get() == 1.0f); + } -// auto t2 = test.find(cif::key("value") == 1.2); -// CHECK(not t2.empty()); -// CHECK(t2.front()["name"].as() == "mies"); -// } + auto t = test.find(cif::key("id") == 1); + REQUIRE(not t.empty()); + CHECK(t.front()["name"].get() == "aap"); -// TEST_CASE("ut3") -// { -// using namespace cif::literals; + auto t2 = test.find(cif::key("value") == 1.2); + REQUIRE(not t2.empty()); + CHECK(t2.front()["name"].get() == "mies"); +} -// auto f = R"(data_TEST -// # -// loop_ -// _test.id -// _test.name -// _test.value -// 1 aap 1.0 -// 2 noot 1.1 -// 3 mies 1.2 -// 4 boom . -// 5 roos ? -// )"_cf; +TEST_CASE("ut3") +{ + using namespace cif::literals; -// auto &db = f.front(); + auto f = R"(data_TEST +# +loop_ +_test.id +_test.name +_test.value +1 aap 1.0 +2 noot 1.1 +3 mies 1.2 +4 boom . +5 roos ? + )"_cf; -// CHECK(db.name() == "TEST"); + auto &db = f.front(); -// auto &test = db["test"]; -// CHECK(test.size() == 5); + CHECK(db.name() == "TEST"); -// CHECK(test.contains("value"_key == cif::null)); -// CHECK(test.find("value"_key == cif::null).size() == 2); -// } + auto &test = db["test"]; + CHECK(test.size() == 5); -// // -------------------------------------------------------------------- + CHECK(test.contains("value"_key == cif::null)); + CHECK(test.find("value"_key == cif::null).size() == 2); +} -// TEST_CASE("compare-with-float") -// { -// auto f = R"(data_TEST -// # -// loop_ -// _test.id -// _test.value -// 1 1.0 -// 2 2.0 -// 3 3.0 -// 4 ? -// 5 . -// )"_cf; +// -------------------------------------------------------------------- -// auto &db = f.front(); -// auto &cat = db["test"]; +TEST_CASE("compare-with-float") +{ + auto f = R"(data_TEST +# +loop_ +_test.id +_test.value +1 1.0 +2 2.0 +3 3.0 +4 ? +5 . + )"_cf; -// CHECK(cat.find_first(cif::key("value") == 1.0, "id") == 1); -// } + auto &db = f.front(); + auto &cat = db["test"]; + + CHECK(cat.find_first(cif::key("value") == 1.0, "id") == 1); +} // // -------------------------------------------------------------------- @@ -714,2713 +732,2757 @@ TEST_CASE("cc_3") // CHECK(test.find1("id"_key == 3, "name") == "mies"); // } -// // -------------------------------------------------------------------- +// -------------------------------------------------------------------- -// TEST_CASE("d1") +TEST_CASE("d1") +{ + const char dict[] = R"( +data_test_dict.dic + _datablock.id test_dict.dic + _datablock.description +; + A test dictionary +; + _dictionary.title test_dict.dic + _dictionary.datablock_id test_dict.dic + _dictionary.version 1.0 + + loop_ + _item_type_list.code + _item_type_list.primitive_code + _item_type_list.construct + _item_type_list.detail + code char + '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' +; code item types/single words ... +; + text char + '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' +; text item types / multi-line text ... +; + int numb + '[+-]?[0-9]+' +; int item types are the subset of numbers that are the negative + or positive integers. +; + +save_cat_1 + _category.description 'A simple test category' + _category.id cat_1 + _category.mandatory_code no + _category_key.name '_cat_1.id' + + save_ + +save__cat_1.id + _item.name '_cat_1.id' + _item.category_id cat_1 + _item.mandatory_code yes + _item_aliases.dictionary cif_core.dic + _item_aliases.version 2.0.1 + _item_linked.child_name '_cat_2.parent_id' + _item_linked.parent_name '_cat_1.id' + _item_type.code code + save_ + +save__cat_1.name + _item.name '_cat_1.name' + _item.category_id cat_1 + _item.mandatory_code yes + _item_aliases.dictionary cif_core.dic + _item_aliases.version 2.0.1 + _item_type.code text + save_ + +save_cat_2 + _category.description 'A second simple test category' + _category.id cat_2 + _category.mandatory_code no + _category_key.name '_cat_2.id' + save_ + +save__cat_2.id + _item.name '_cat_2.id' + _item.category_id cat_2 + _item.mandatory_code yes + _item_aliases.dictionary cif_core.dic + _item_aliases.version 2.0.1 + _item_type.code int + save_ + +save__cat_2.parent_id + _item.name '_cat_2.parent_id' + _item.category_id cat_2 + _item.mandatory_code yes + _item_aliases.dictionary cif_core.dic + _item_aliases.version 2.0.1 + _item_type.code code + save_ + +save__cat_2.desc + _item.name '_cat_2.desc' + _item.category_id cat_2 + _item.mandatory_code yes + _item_aliases.dictionary cif_core.dic + _item_aliases.version 2.0.1 + _item_type.code text + save_ + )"; + + struct membuf : public std::streambuf + { + membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } buffer(const_cast(dict), sizeof(dict) - 1); + + std::istream is_dict(&buffer); + + cif::validator validator(is_dict); + + cif::file f; + + // -------------------------------------------------------------------- + + const char data[] = R"( +data_test +loop_ +_cat_1.id +_cat_1.name +1 Aap +2 Noot +3 Mies + +loop_ +_cat_2.id +_cat_2.parent_id +_cat_2.desc +1 1 'Een dier' +2 1 'Een andere aap' +3 2 'walnoot bijvoorbeeld' + )"; + + struct data_membuf : public std::streambuf + { + data_membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } data_buffer(const_cast(data), sizeof(data) - 1); + + std::istream is_data(&data_buffer); + f.load(is_data, validator); + + SECTION("one") + { + auto &cat1 = f.front()["cat_1"]; + auto &cat2 = f.front()["cat_2"]; + + CHECK(cat1.size() == 3); + CHECK(cat2.size() == 3); + + cat1.erase(cif::key("id") == 1); + + CHECK(cat1.size() == 2); + CHECK(cat2.size() == 1); + + // CHECK_THROWS_AS(cat2.emplace({ + // { "id", 4 }, + // { "parent_id", 4 }, + // { "desc", "moet fout gaan" } + // }), std::exception); + + CHECK_THROWS_AS(cat2.emplace({ { "id", "vijf" }, // <- invalid value + { "parent_id", 2 }, + { "desc", "moet fout gaan" } }), + std::exception); + } + + // SECTION("two") + // { + // auto &cat1 = f.front()["cat_1"]; + // auto &cat2 = f.front()["cat_2"]; + + // cat1.update_value(cif::all(), "id", [](std::string_view v) -> std::string + // { + // int vi; + // auto [ec, ptr] = std::from_chars(v.data(), v.data() + v.length(), vi); + // return std::to_string(vi + 1); + // }); + + // CHECK(cat1.find1(cif::key("id") == 2, "name") == "Aap"); + // CHECK(cat1.find1(cif::key("id") == 3, "name") == "Noot"); + // CHECK(cat1.find1(cif::key("id") == 4, "name") == "Mies"); + + // CHECK(cat2.find1(cif::key("id") == 1, "parent_id") == 2); + // CHECK(cat2.find1(cif::key("id") == 2, "parent_id") == 2); + // CHECK(cat2.find1(cif::key("id") == 3, "parent_id") == 3); + // } +} + +// -------------------------------------------------------------------- + +TEST_CASE("d2") +{ + const char dict[] = R"( +data_test_dict.dic + _datablock.id test_dict.dic + _datablock.description +; + A test dictionary +; + _dictionary.title test_dict.dic + _dictionary.datablock_id test_dict.dic + _dictionary.version 1.0 + + loop_ + _item_type_list.code + _item_type_list.primitive_code + _item_type_list.construct + _item_type_list.detail + code char + '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' +; code item types/single words ... +; + ucode uchar + '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' +; code item types/single words, case insensitive +; + text char + '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' +; text item types / multi-line text ... +; + int numb + '[+-]?[0-9]+' +; int item types are the subset of numbers that are the negative + or positive integers. +; + +save_cat_1 + _category.description 'A simple test category' + _category.id cat_1 + _category.mandatory_code no + _category_key.name '_cat_1.id' + save_ + +save__cat_1.id + _item.name '_cat_1.id' + _item.category_id cat_1 + _item.mandatory_code yes + _item_type.code code + save_ + +save__cat_1.c + _item.name '_cat_1.c' + _item.category_id cat_1 + _item.mandatory_code yes + _item_type.code ucode + save_ +)"; + + struct membuf : public std::streambuf + { + membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } buffer(const_cast(dict), sizeof(dict) - 1); + + std::istream is_dict(&buffer); + + cif::validator validator(is_dict); + + cif::file f; + + // -------------------------------------------------------------------- + + const char data[] = R"( +data_test +loop_ +_cat_1.id +_cat_1.c +aap Aap +noot Noot +mies Mies +)"; + + struct data_membuf : public std::streambuf + { + data_membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } data_buffer(const_cast(data), sizeof(data) - 1); + + std::istream is_data(&data_buffer); + f.load(is_data, validator); + + auto &cat1 = f.front()["cat_1"]; + + CHECK(cat1.size() == 3); + + cat1.erase(cif::key("id") == "AAP"); + + CHECK(cat1.size() == 3); + + cat1.erase(cif::key("id") == "noot"); + + CHECK(cat1.size() == 2); + + // should fail with duplicate key: + CHECK_THROWS_AS(cat1.emplace({ { "id", "aap" }, + { "c", "2e-aap" } }), + std::exception); + + cat1.erase(cif::key("id") == "aap"); + + CHECK(cat1.size() == 1); + + cat1.emplace({ { "id", "aap" }, + { "c", "2e-aap" } }); + + CHECK(cat1.size() == 2); +} + +// -------------------------------------------------------------------- + +TEST_CASE("d3") +{ + const char dict[] = R"( +data_test_dict.dic + _datablock.id test_dict.dic + _datablock.description +; + A test dictionary +; + _dictionary.title test_dict.dic + _dictionary.datablock_id test_dict.dic + _dictionary.version 1.0 + + loop_ + _item_type_list.code + _item_type_list.primitive_code + _item_type_list.construct + code char + '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' + + text char + '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' + + int numb + '[+-]?[0-9]+' + +save_cat_1 + _category.description 'A simple test category' + _category.id cat_1 + _category.mandatory_code no + _category_key.name '_cat_1.id' + + save_ + +save__cat_1.id + _item.name '_cat_1.id' + _item.category_id cat_1 + _item.mandatory_code yes + _item_linked.child_name '_cat_2.parent_id' + _item_linked.parent_name '_cat_1.id' + _item_type.code code + save_ + +save__cat_1.name1 + _item.name '_cat_1.name1' + _item.category_id cat_1 + _item.mandatory_code yes + _item_type.code text + save_ + +save__cat_1.name2 + _item.name '_cat_1.name2' + _item.category_id cat_1 + _item.mandatory_code no + _item_linked.child_name '_cat_2.name2' + _item_linked.parent_name '_cat_1.name2' + _item_type.code text + save_ + +save_cat_2 + _category.description 'A second simple test category' + _category.id cat_2 + _category.mandatory_code no + _category_key.name '_cat_2.id' + save_ + +save__cat_2.id + _item.name '_cat_2.id' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_2.parent_id + _item.name '_cat_2.parent_id' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code code + save_ + +save__cat_2.name2 + _item.name '_cat_2.name2' + _item.category_id cat_2 + _item.mandatory_code no + _item_type.code text + save_ + +save__cat_2.desc + _item.name '_cat_2.desc' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code text + save_ + )"; + + struct membuf : public std::streambuf + { + membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } buffer(const_cast(dict), sizeof(dict) - 1); + + std::istream is_dict(&buffer); + + cif::validator validator(is_dict); + + cif::file f; + + // -------------------------------------------------------------------- + + const char data[] = R"( +data_test +loop_ +_cat_1.id +_cat_1.name1 +_cat_1.name2 +1 Aap aap +2 Noot noot +3 Mies mies + +loop_ +_cat_2.id +_cat_2.parent_id +_cat_2.name2 +_cat_2.desc +1 1 aap 'Een dier' +2 1 . 'Een andere aap' +3 2 noot 'walnoot bijvoorbeeld' +4 2 n2 hazelnoot + )"; + + struct data_membuf : public std::streambuf + { + data_membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } data_buffer(const_cast(data), sizeof(data) - 1); + + std::istream is_data(&data_buffer); + f.load(is_data, validator); + + auto &cat1 = f.front()["cat_1"]; + auto &cat2 = f.front()["cat_2"]; + + // check a rename in parent and child + + for (auto r : cat1.find(cif::key("id") == 1)) + { + r["id"] = 10; + break; + } + + CHECK(cat1.size() == 3); + CHECK(cat2.size() == 4); + + CHECK(cat1.find(cif::key("id") == 1).size() == 0); + CHECK(cat1.find(cif::key("id") == 10).size() == 1); + + CHECK(cat2.find(cif::key("parent_id") == 1).size() == 0); + CHECK(cat2.find(cif::key("parent_id") == 10).size() == 2); + + // check a rename in parent and child, this time only one child should be renamed + + for (auto r : cat1.find(cif::key("id") == 2)) + { + r["id"] = 20; + break; + } + + CHECK(cat1.size() == 3); + CHECK(cat2.size() == 4); + + CHECK(cat1.find(cif::key("id") == 2).size() == 0); + CHECK(cat1.find(cif::key("id") == 20).size() == 1); + + CHECK(cat2.find(cif::key("parent_id") == 2).size() == 1); + CHECK(cat2.find(cif::key("parent_id") == 20).size() == 1); + + CHECK(cat2.find(cif::key("parent_id") == 2 and cif::key("name2") == "noot").size() == 0); + CHECK(cat2.find(cif::key("parent_id") == 2 and cif::key("name2") == "n2").size() == 1); + CHECK(cat2.find(cif::key("parent_id") == 20 and cif::key("name2") == "noot").size() == 1); + CHECK(cat2.find(cif::key("parent_id") == 20 and cif::key("name2") == "n2").size() == 0); + + // -------------------------------------------------------------------- + + cat1.erase(cif::key("id") == 10); + + CHECK(cat1.size() == 2); + CHECK(cat2.size() == 2); // TODO: Is this really what we want? + + cat1.erase(cif::key("id") == 20); + + CHECK(cat1.size() == 1); + CHECK(cat2.size() == 1); // TODO: Is this really what we want? +} + +// -------------------------------------------------------------------- + +TEST_CASE("d4") +{ + const char dict[] = R"( +data_test_dict.dic + _datablock.id test_dict.dic + _datablock.description +; + A test dictionary +; + _dictionary.title test_dict.dic + _dictionary.datablock_id test_dict.dic + _dictionary.version 1.0 + + loop_ + _item_type_list.code + _item_type_list.primitive_code + _item_type_list.construct + code char + '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' + + text char + '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' + + int numb + '[+-]?[0-9]+' + +save_cat_1 + _category.description 'A simple test category' + _category.id cat_1 + _category.mandatory_code no + _category_key.name '_cat_1.id' + save_ + +save__cat_1.id + _item.name '_cat_1.id' + _item.category_id cat_1 + _item.mandatory_code yes + _item_linked.child_name '_cat_2.parent_id' + _item_linked.parent_name '_cat_1.id' + _item_type.code int + save_ + +save__cat_1.id2 + _item.name '_cat_1.id2' + _item.category_id cat_1 + _item.mandatory_code no + _item_linked.child_name '_cat_2.parent_id2' + _item_linked.parent_name '_cat_1.id2' + _item_type.code code + save_ + +save__cat_1.id3 + _item.name '_cat_1.id3' + _item.category_id cat_1 + _item.mandatory_code no + _item_linked.child_name '_cat_2.parent_id3' + _item_linked.parent_name '_cat_1.id3' + _item_type.code text + save_ + +save_cat_2 + _category.description 'A second simple test category' + _category.id cat_2 + _category.mandatory_code no + _category_key.name '_cat_2.id' + save_ + +save__cat_2.id + _item.name '_cat_2.id' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_2.parent_id + _item.name '_cat_2.parent_id' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_2.parent_id2 + _item.name '_cat_2.parent_id2' + _item.category_id cat_2 + _item.mandatory_code no + _item_type.code code + save_ + +save__cat_2.parent_id3 + _item.name '_cat_2.parent_id3' + _item.category_id cat_2 + _item.mandatory_code no + _item_type.code code + save_ + + )"; + + struct membuf : public std::streambuf + { + membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } buffer(const_cast(dict), sizeof(dict) - 1); + + std::istream is_dict(&buffer); + + cif::validator validator(is_dict); + + cif::file f; + + // -------------------------------------------------------------------- + + const char data[] = R"( +data_test +loop_ +_cat_1.id +_cat_1.id2 +_cat_1.id3 +1 aap aap +2 . noot +3 mies . +4 . . + +loop_ +_cat_2.id +_cat_2.parent_id +_cat_2.parent_id2 +_cat_2.parent_id3 + 1 1 aap aap + 2 1 . x + 3 1 aap . + 4 2 noot noot + 5 2 . noot + 6 2 noot . + 7 2 . . + 8 3 mies mies + 9 3 . mies +10 3 mies . +11 4 roos roos +12 4 . roos +13 4 roos . + )"; + + struct data_membuf : public std::streambuf + { + data_membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } data_buffer(const_cast(data), sizeof(data) - 1); + + std::istream is_data(&data_buffer); + f.load(is_data, validator); + + auto &cat1 = f.front()["cat_1"]; + auto &cat2 = f.front()["cat_2"]; + + // check a rename in parent and child + + for (auto r : cat1.find(cif::key("id") == 1)) + { + r["id"] = 10; + break; + } + + CHECK(cat1.size() == 4); + CHECK(cat2.size() == 13); + + CHECK(cat1.find(cif::key("id") == 1).size() == 0); + CHECK(cat1.find(cif::key("id") == 10).size() == 1); + + CHECK(cat2.find(cif::key("parent_id") == 1).size() == 1); + CHECK(cat2.find(cif::key("parent_id") == 10).size() == 2); + + for (auto r : cat1.find(cif::key("id") == 2)) + { + r["id"] = 20; + break; + } + + CHECK(cat1.size() == 4); + CHECK(cat2.size() == 13); + + CHECK(cat1.find(cif::key("id") == 2).size() == 0); + CHECK(cat1.find(cif::key("id") == 20).size() == 1); + + CHECK(cat2.find(cif::key("parent_id") == 2).size() == 2); + CHECK(cat2.find(cif::key("parent_id") == 20).size() == 2); + + for (auto r : cat1.find(cif::key("id") == 3)) + { + r["id"] = 30; + break; + } + + CHECK(cat1.size() == 4); + CHECK(cat2.size() == 13); + + CHECK(cat1.find(cif::key("id") == 3).size() == 0); + CHECK(cat1.find(cif::key("id") == 30).size() == 1); + + CHECK(cat2.find(cif::key("parent_id") == 3).size() == 2); + CHECK(cat2.find(cif::key("parent_id") == 30).size() == 1); + + for (auto r : cat1.find(cif::key("id") == 4)) + { + r["id"] = 40; + break; + } + + CHECK(cat1.size() == 4); + CHECK(cat2.size() == 13); + + CHECK(cat1.find(cif::key("id") == 4).size() == 0); + CHECK(cat1.find(cif::key("id") == 10).size() == 1); + + CHECK(cat2.find(cif::key("parent_id") == 4).size() == 3); + CHECK(cat2.find(cif::key("parent_id") == 40).size() == 0); +} + +// -------------------------------------------------------------------- + +TEST_CASE("d5") +{ + const char dict[] = R"( +data_test_dict.dic + _datablock.id test_dict.dic + _datablock.description +; + A test dictionary +; + _dictionary.title test_dict.dic + _dictionary.datablock_id test_dict.dic + _dictionary.version 1.0 + + loop_ + _item_type_list.code + _item_type_list.primitive_code + _item_type_list.construct + code char + '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' + + text char + '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' + + int numb + '[+-]?[0-9]+' + +save_cat_1 + _category.description 'A simple test category' + _category.id cat_1 + _category.mandatory_code no + _category_key.name '_cat_1.id' + save_ + +save__cat_1.id + _item.name '_cat_1.id' + _item.category_id cat_1 + _item.mandatory_code yes + _item_type.code int + save_ + +save_cat_2 + _category.description 'A second simple test category' + _category.id cat_2 + _category.mandatory_code no + _category_key.name '_cat_2.id' + save_ + +save__cat_2.id + _item.name '_cat_2.id' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_2.parent_id + _item.name '_cat_2.parent_id' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_2.parent_id2 + _item.name '_cat_2.parent_id2' + _item.category_id cat_2 + _item.mandatory_code no + _item_type.code code + save_ + +save__cat_2.parent_id3 + _item.name '_cat_2.parent_id3' + _item.category_id cat_2 + _item.mandatory_code no + _item_type.code code + save_ + +loop_ +_pdbx_item_linked_group_list.child_category_id +_pdbx_item_linked_group_list.link_group_id +_pdbx_item_linked_group_list.child_name +_pdbx_item_linked_group_list.parent_name +_pdbx_item_linked_group_list.parent_category_id +cat_2 1 '_cat_2.parent_id' '_cat_1.id' cat_1 +cat_2 2 '_cat_2.parent_id2' '_cat_1.id' cat_1 +cat_2 3 '_cat_2.parent_id3' '_cat_1.id' cat_1 + +loop_ +_pdbx_item_linked_group.category_id +_pdbx_item_linked_group.link_group_id +_pdbx_item_linked_group.label +cat_2 1 cat_2:cat_1:1 +cat_2 2 cat_2:cat_1:2 +cat_2 3 cat_2:cat_1:3 + )"; + + struct membuf : public std::streambuf + { + membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } buffer(const_cast(dict), sizeof(dict) - 1); + + std::istream is_dict(&buffer); + + cif::validator validator(is_dict); + + cif::file f; + + // -------------------------------------------------------------------- + + const char data[] = R"( +data_test +loop_ +_cat_1.id +1 +2 +3 + +loop_ +_cat_2.id +_cat_2.parent_id +_cat_2.parent_id2 +_cat_2.parent_id3 + 1 1 ? ? + 2 ? 1 ? + 3 ? ? 1 + 4 2 2 ? + 5 2 ? 2 + 6 ? 2 2 + 7 3 3 3 + )"; + + // -------------------------------------------------------------------- + + struct data_membuf : public std::streambuf + { + data_membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } data_buffer(const_cast(data), sizeof(data) - 1); + + std::istream is_data(&data_buffer); + f.load(is_data, validator); + + auto &cat1 = f.front()["cat_1"]; + auto &cat2 = f.front()["cat_2"]; + + // -------------------------------------------------------------------- + // check iterate children + + auto PR2set = cat1.find(cif::key("id") == 2); + REQUIRE(PR2set.size() == 1); + auto PR2 = PR2set.front(); + CHECK(PR2["id"].get() == 2); + + auto CR2set = cat1.get_children(PR2, cat2); + CHECK(CR2set.size() == 3); + CHECK(CR2set.size() == 3); + + std::vector CRids; + std::transform(CR2set.begin(), CR2set.end(), std::back_inserter(CRids), [](cif::row_handle r) + { return r["id"].get(); }); + std::sort(CRids.begin(), CRids.end()); + CHECK(CRids == std::vector({ 4, 5, 6 })); + + // check a rename in parent and child + + for (auto r : cat1.find(cif::key("id") == 1)) + { + r["id"] = 10; + break; + } + + CHECK(cat1.size() == 3); + CHECK(cat2.size() == 7); + + CHECK(cat1.find(cif::key("id") == 1).size() == 0); + CHECK(cat1.find(cif::key("id") == 10).size() == 1); + + CHECK(cat2.find(cif::key("parent_id") == 1).size() == 0); + CHECK(cat2.find(cif::key("parent_id2") == 1).size() == 0); + CHECK(cat2.find(cif::key("parent_id3") == 1).size() == 0); + CHECK(cat2.find(cif::key("parent_id") == 10).size() == 1); + CHECK(cat2.find(cif::key("parent_id2") == 10).size() == 1); + CHECK(cat2.find(cif::key("parent_id3") == 10).size() == 1); + + for (auto r : cat1.find(cif::key("id") == 2)) + { + r["id"] = 20; + break; + } + + CHECK(cat1.size() == 3); + CHECK(cat2.size() == 7); + + CHECK(cat1.find(cif::key("id") == 2).size() == 0); + CHECK(cat1.find(cif::key("id") == 20).size() == 1); + + CHECK(cat2.find(cif::key("parent_id") == 2).size() == 0); + CHECK(cat2.find(cif::key("parent_id2") == 2).size() == 0); + CHECK(cat2.find(cif::key("parent_id3") == 2).size() == 0); + CHECK(cat2.find(cif::key("parent_id") == 20).size() == 2); + CHECK(cat2.find(cif::key("parent_id2") == 20).size() == 2); + CHECK(cat2.find(cif::key("parent_id3") == 20).size() == 2); + + for (auto r : cat1.find(cif::key("id") == 3)) + { + r["id"] = 30; + break; + } + + CHECK(cat1.size() == 3); + CHECK(cat2.size() == 7); + + CHECK(cat1.find(cif::key("id") == 3).size() == 0); + CHECK(cat1.find(cif::key("id") == 30).size() == 1); + + CHECK(cat2.find(cif::key("parent_id") == 3).size() == 0); + CHECK(cat2.find(cif::key("parent_id2") == 3).size() == 0); + CHECK(cat2.find(cif::key("parent_id3") == 3).size() == 0); + CHECK(cat2.find(cif::key("parent_id") == 30).size() == 1); + CHECK(cat2.find(cif::key("parent_id2") == 30).size() == 1); + CHECK(cat2.find(cif::key("parent_id3") == 30).size() == 1); + + // test delete + + cat1.erase(cif::key("id") == 10); + CHECK(cat1.size() == 2); + CHECK(cat2.size() == 4); + + cat1.erase(cif::key("id") == 20); + CHECK(cat1.size() == 1); + CHECK(cat2.size() == 1); + + cat1.erase(cif::key("id") == 30); + CHECK(cat1.size() == 0); + CHECK(cat2.size() == 0); +} + +// -------------------------------------------------------------------- + +TEST_CASE("d6") +{ + const char dict[] = R"( +data_test_dict.dic + _datablock.id test_dict.dic + _datablock.description +; + A test dictionary +; + _dictionary.title test_dict.dic + _dictionary.datablock_id test_dict.dic + _dictionary.version 1.0 + + loop_ + _item_type_list.code + _item_type_list.primitive_code + _item_type_list.construct + code char + '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' + + text char + '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' + + int numb + '[+-]?[0-9]+' + +save_cat_1 + _category.description 'A simple test category' + _category.id cat_1 + _category.mandatory_code yes + _category_key.name '_cat_1.id' + save_ + +save__cat_1.id + _item.name '_cat_1.id' + _item.category_id cat_1 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_1.id_2 + _item.name '_cat_1.id_2' + _item.category_id cat_1 + _item.mandatory_code no + _item_type.code int + save_ + +save_cat_2 + _category.description 'A second simple test category' + _category.id cat_2 + _category.mandatory_code no + _category_key.name '_cat_2.id' + save_ + +save__cat_2.id + _item.name '_cat_2.id' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_2.parent_id + _item.name '_cat_2.parent_id' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_2.parent_id_2 + _item.name '_cat_2.parent_id_2' + _item.category_id cat_2 + _item.mandatory_code no + _item_type.code code + save_ + +loop_ +_pdbx_item_linked_group_list.child_category_id +_pdbx_item_linked_group_list.link_group_id +_pdbx_item_linked_group_list.child_name +_pdbx_item_linked_group_list.parent_name +_pdbx_item_linked_group_list.parent_category_id +cat_2 1 '_cat_2.parent_id' '_cat_1.id' cat_1 +cat_2 1 '_cat_2.parent_id_2' '_cat_1.id_2' cat_1 + +loop_ +_pdbx_item_linked_group.category_id +_pdbx_item_linked_group.link_group_id +_pdbx_item_linked_group.label +cat_2 1 cat_2:cat_1:1 + )"; + + struct membuf : public std::streambuf + { + membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } buffer(const_cast(dict), sizeof(dict) - 1); + + std::istream is_dict(&buffer); + + cif::validator validator(is_dict); + + cif::file f; + + // -------------------------------------------------------------------- + + const char data[] = R"( +data_test +loop_ +_cat_1.id +_cat_1.id_2 +1 1 +2 2 +3 ? + +loop_ +_cat_2.id +_cat_2.parent_id +_cat_2.parent_id_2 + 0 1 1 + 1 1 ? + 2 ? 1 + 3 ? ? + 4 2 2 + 5 3 1 + 6 3 ? + )"; + + // -------------------------------------------------------------------- + + struct data_membuf : public std::streambuf + { + data_membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } data_buffer(const_cast(data), sizeof(data) - 1); + + std::istream is_data(&data_buffer); + f.load(is_data, validator); + + // auto &cat1 = f.front()["cat_1"]; + auto &cat2 = f.front()["cat_2"]; + + // f.front().validate_links(); + // CHECK(not ); + + using namespace cif::literals; + + CHECK( cat2.has_parents(cat2.find1("id"_key == 0))); + CHECK( cat2.has_parents(cat2.find1("id"_key == 1))); + CHECK( cat2.has_parents(cat2.find1("id"_key == 2))); + CHECK(not cat2.has_parents(cat2.find1("id"_key == 3))); + CHECK( cat2.has_parents(cat2.find1("id"_key == 4))); + CHECK(not cat2.has_parents(cat2.find1("id"_key == 5))); + CHECK( cat2.has_parents(cat2.find1("id"_key == 6))); +} + +// -------------------------------------------------------------------- + +TEST_CASE("c1") +{ + cif::VERBOSE = 1; + + auto f = R"(data_TEST +# +loop_ +_test.id +_test.name +1 aap +2 noot +3 mies +4 . +5 ? + )"_cf; + + auto &db = f.front(); + + for (auto r : db["test"].find(cif::key("id") == 1)) + { + const auto &[id, name] = r.get("id", "name"); + CHECK(id == 1); + CHECK(name == "aap"); + } + + for (auto r : db["test"].find(cif::key("id") == 4)) + { + const auto &[id, name] = r.get("id", "name"); + CHECK(id == 4); + CHECK(name.empty()); + } + + for (auto r : db["test"].find(cif::key("id") == 5)) + { + const auto &[id, name] = r.get("id", "name"); + CHECK(id == 5); + CHECK(name.empty()); + } + + // optional + + for (auto r : db["test"]) + { + const auto &[id, name] = r.get>("id", "name"); + switch (id) + { + case 1: CHECK(name == "aap"); break; + case 2: CHECK(name == "noot"); break; + case 3: CHECK(name == "mies"); break; + case 4: + case 5: CHECK(not name); break; + default: + CHECK(false); + } + } +} + +TEST_CASE("c2") +{ + cif::VERBOSE = 1; + + auto f = R"(data_TEST +# +loop_ +_test.id +_test.name +1 aap +2 noot +3 mies +4 . +5 ? + )"_cf; + + auto &db = f.front(); + + // query tests + + for (const auto &[id, name] : db["test"].rows>("id", "name")) + { + switch (id) + { + case 1: CHECK(name == "aap"); break; + case 2: CHECK(name == "noot"); break; + case 3: CHECK(name == "mies"); break; + case 4: + case 5: CHECK(not name); break; + default: + CHECK(false); + } + } +} + +TEST_CASE("c3") +{ + auto f = R"(data_TEST +# +loop_ +_test.id +_test.name +1 aap +2 noot +3 mies +4 . +5 ? + )"_cf; + + auto &db = f.front(); + + // query tests + for (const auto &[id, name] : db["test"].find>(cif::all(), "id", "name")) + { + switch (id) + { + case 1: CHECK(name == "aap"); break; + case 2: CHECK(name == "noot"); break; + case 3: CHECK(name == "mies"); break; + case 4: + case 5: CHECK(not name); break; + default: + CHECK(false); + } + } + + const auto &[id, name] = db["test"].find1(cif::key("id") == 1, "id", "name"); + + CHECK(id == 1); + CHECK(name == "aap"); +} + +TEST_CASE("c4") +{ + auto f = R"(data_TEST +# +loop_ +_test.id +_test.name +1 aap +2 noot +3 mies +4 . +5 ? + )"_cf; + + auto &db = f.front(); + + // query tests + CHECK(db["test"].find_max("id") == 5); + CHECK(db["test"].find_max("id", cif::key("name") != cif::null) == 3); + + CHECK(db["test"].find_min("id") == 1); + CHECK(db["test"].find_min("id", cif::key("name") == cif::null) == 4); + + // count tests + CHECK(db["test"].count(cif::all()) == 5); + CHECK(db["test"].count(cif::key("name") != cif::null) == 3); + CHECK(db["test"].count(cif::key("name") == cif::null) == 2); + + // find_first tests + CHECK(db["test"].find_first(cif::key("id") == 1, "id") == 1); + CHECK(db["test"].find_first(cif::all(), "id") == 1); + + std::optional v; + + v = db["test"].find_first>(cif::key("id") == 1, "id"); + CHECK(v.has_value()); + CHECK(*v == 1); + + v = db["test"].find_first>(cif::key("id") == 6, "id"); + CHECK(not v.has_value()); + + // find1 tests + CHECK(db["test"].find1(cif::key("id") == 1, "id") == 1); + CHECK_THROWS_AS(db["test"].find1(cif::all(), "id"), cif::multiple_results_error); +} + +// -------------------------------------------------------------------- +// rename test + +TEST_CASE("r1") +{ + /* + Rationale: + + The pdbx_mmcif dictionary contains inconsistent child-parent relations. E.g. atom_site is parent + of pdbx_nonpoly_scheme which itself is a parent of pdbx_entity_nonpoly. If I want to rename a residue + I cannot update pdbx_nonpoly_scheme since changing a parent changes children, but not vice versa. + + But if I change the comp_id in atom_site, the pdbx_nonpoly_scheme is updated, that's good, and then + pdbx_entity_nonpoly is updated and that's bad. + + The idea is now that if we update a parent and a child that must change as well, we first check + if there are more parents of this child that will not change. In that case we have to split the + child into two, one with the new value and one with the old. We then of course have to split all + children of this split row that are direct children. + */ + + const char dict[] = R"( +data_test_dict.dic + _datablock.id test_dict.dic + _datablock.description +; + A test dictionary +; + _dictionary.title test_dict.dic + _dictionary.datablock_id test_dict.dic + _dictionary.version 1.0 + + loop_ + _item_type_list.code + _item_type_list.primitive_code + _item_type_list.construct + code char + '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' + + text char + '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' + + int numb + '[+-]?[0-9]+' + +save_cat_1 + _category.description 'A simple test category' + _category.id cat_1 + _category.mandatory_code no + _category_key.name '_cat_1.id' + save_ + +save__cat_1.id + _item.name '_cat_1.id' + _item.category_id cat_1 + _item.mandatory_code yes + _item_linked.child_name '_cat_2.parent_id' + _item_linked.parent_name '_cat_1.id' + _item_type.code int + save_ + +save__cat_1.name + _item.name '_cat_1.name' + _item.category_id cat_1 + _item.mandatory_code yes + _item_type.code code + save_ + +save__cat_1.desc + _item.name '_cat_1.desc' + _item.category_id cat_1 + _item.mandatory_code yes + _item_type.code text + save_ + +save_cat_2 + _category.description 'A second simple test category' + _category.id cat_2 + _category.mandatory_code no + _category_key.name '_cat_2.id' + save_ + +save__cat_2.id + _item.name '_cat_2.id' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_2.name + _item.name '_cat_2.name' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code code + save_ + +save__cat_2.num + _item.name '_cat_2.num' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_2.desc + _item.name '_cat_2.desc' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code text + save_ + +save_cat_3 + _category.description 'A third simple test category' + _category.id cat_3 + _category.mandatory_code no + _category_key.name '_cat_3.id' + save_ + +save__cat_3.id + _item.name '_cat_3.id' + _item.category_id cat_3 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_3.name + _item.name '_cat_3.name' + _item.category_id cat_3 + _item.mandatory_code yes + _item_type.code code + save_ + +save__cat_3.num + _item.name '_cat_3.num' + _item.category_id cat_3 + _item.mandatory_code yes + _item_type.code int + save_ + +loop_ +_pdbx_item_linked_group_list.child_category_id +_pdbx_item_linked_group_list.link_group_id +_pdbx_item_linked_group_list.child_name +_pdbx_item_linked_group_list.parent_name +_pdbx_item_linked_group_list.parent_category_id +cat_1 1 '_cat_1.name' '_cat_2.name' cat_2 +cat_2 1 '_cat_2.name' '_cat_3.name' cat_3 +cat_2 1 '_cat_2.num' '_cat_3.num' cat_3 + + )"; + + struct membuf : public std::streambuf + { + membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } buffer(const_cast(dict), sizeof(dict) - 1); + + std::istream is_dict(&buffer); + + cif::validator validator(is_dict); + + cif::file f; + + // -------------------------------------------------------------------- + + const char data[] = R"( +data_test +loop_ +_cat_1.id +_cat_1.name +_cat_1.desc +1 aap Aap +2 noot Noot +3 mies Mies + +loop_ +_cat_2.id +_cat_2.name +_cat_2.num +_cat_2.desc +1 aap 1 'Een dier' +2 aap 2 'Een andere aap' +3 noot 1 'walnoot bijvoorbeeld' + +loop_ +_cat_3.id +_cat_3.name +_cat_3.num +1 aap 1 +2 aap 2 + )"; + + using namespace cif::literals; + + struct data_membuf : public std::streambuf + { + data_membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } data_buffer(const_cast(data), sizeof(data) - 1); + + std::istream is_data(&data_buffer); + f.load(is_data, validator); + + auto &cat1 = f.front()["cat_1"]; + auto &cat2 = f.front()["cat_2"]; + auto &cat3 = f.front()["cat_3"]; + + cat3.update_value("name"_key == "aap" and "num"_key == 1, "name", "aapje"); + + CHECK(cat3.size() == 2); + + { + int id, num; + std::string name; + cif::tie(id, name, num) = cat3.front().get("id", "name", "num"); + CHECK(id == 1); + CHECK(num == 1); + CHECK(name == "aapje"); + + cif::tie(id, name, num) = cat3.back().get("id", "name", "num"); + CHECK(id == 2); + CHECK(num == 2); + CHECK(name == "aap"); + } + + int i = 0; + for (const auto &[id, name, num, desc] : cat2.rows("id", "name", "num", "desc")) + { + switch (++i) + { + case 1: + CHECK(id == 1); + CHECK(num == 1); + CHECK(name == "aapje"); + CHECK(desc == "Een dier"); + break; + + case 2: + CHECK(id == 2); + CHECK(num == 2); + CHECK(name == "aap"); + CHECK(desc == "Een andere aap"); + break; + + case 3: + CHECK(id == 3); + CHECK(num == 1); + CHECK(name == "noot"); + CHECK(desc == "walnoot bijvoorbeeld"); + break; + + default: + CHECK(false /*"Unexpected record"*/); + } + } + + CHECK(cat1.size() == 4); + i = 0; + for (const auto &[id, name, desc] : cat1.rows("id", "name", "desc")) + { + switch (++i) + { + case 1: + CHECK(id == 1); + CHECK(name == "aapje"); + CHECK(desc == "Aap"); + break; + + case 2: + CHECK(id == 2); + CHECK(name == "noot"); + CHECK(desc == "Noot"); + break; + + case 3: + CHECK(id == 3); + CHECK(name == "mies"); + CHECK(desc == "Mies"); + break; + + case 4: + CHECK(id == 4); + CHECK(name == "aap"); + CHECK(desc == "Aap"); + break; + + default: + CHECK(false /* "Unexpected record" */); + } + } + + // f.save(std::cout); +} + +TEST_CASE("pc_1") +{ + /* + Parent/child tests + + Note that the dictionary is different than the one in test r1 + */ + + const char dict[] = R"( +data_test_dict.dic + _datablock.id test_dict.dic + _datablock.description +; + A test dictionary +; + _dictionary.title test_dict.dic + _dictionary.datablock_id test_dict.dic + _dictionary.version 1.0 + + loop_ + _item_type_list.code + _item_type_list.primitive_code + _item_type_list.construct + code char + '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' + + text char + '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' + + int numb + '[+-]?[0-9]+' + +save_cat_1 + _category.description 'A simple test category' + _category.id cat_1 + _category.mandatory_code no + _category_key.name '_cat_1.id' + save_ + +save__cat_1.id + _item.name '_cat_1.id' + _item.category_id cat_1 + _item.mandatory_code yes + _item_linked.child_name '_cat_2.parent_id' + _item_linked.parent_name '_cat_1.id' + _item_type.code int + save_ + +save__cat_1.name + _item.name '_cat_1.name' + _item.category_id cat_1 + _item.mandatory_code yes + _item_type.code code + save_ + +save__cat_1.desc + _item.name '_cat_1.desc' + _item.category_id cat_1 + _item.mandatory_code yes + _item_type.code text + save_ + +save_cat_2 + _category.description 'A second simple test category' + _category.id cat_2 + _category.mandatory_code no + _category_key.name '_cat_2.id' + save_ + +save__cat_2.id + _item.name '_cat_2.id' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_2.name + _item.name '_cat_2.name' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code code + save_ + +save__cat_2.num + _item.name '_cat_2.num' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_2.desc + _item.name '_cat_2.desc' + _item.category_id cat_2 + _item.mandatory_code yes + _item_type.code text + save_ + +save_cat_3 + _category.description 'A third simple test category' + _category.id cat_3 + _category.mandatory_code no + _category_key.name '_cat_3.id' + save_ + +save__cat_3.id + _item.name '_cat_3.id' + _item.category_id cat_3 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_3.name + _item.name '_cat_3.name' + _item.category_id cat_3 + _item.mandatory_code yes + _item_type.code code + save_ + +save__cat_3.num + _item.name '_cat_3.num' + _item.category_id cat_3 + _item.mandatory_code yes + _item_type.code int + save_ + +loop_ +_pdbx_item_linked_group_list.parent_category_id +_pdbx_item_linked_group_list.link_group_id +_pdbx_item_linked_group_list.parent_name +_pdbx_item_linked_group_list.child_name +_pdbx_item_linked_group_list.child_category_id +cat_1 1 '_cat_1.name' '_cat_2.name' cat_2 +cat_2 1 '_cat_2.name' '_cat_3.name' cat_3 +cat_2 1 '_cat_2.num' '_cat_3.num' cat_3 + + )"; + + struct membuf : public std::streambuf + { + membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } buffer(const_cast(dict), sizeof(dict) - 1); + + std::istream is_dict(&buffer); + + cif::validator validator(is_dict); + + cif::file f; + + // -------------------------------------------------------------------- + + const char data[] = R"( +data_test +loop_ +_cat_1.id +_cat_1.name +_cat_1.desc +1 aap Aap +2 noot Noot +3 mies Mies + +loop_ +_cat_2.id +_cat_2.name +_cat_2.num +_cat_2.desc +1 aap 1 'Een dier' +2 aap 2 'Een andere aap' +3 noot 1 'walnoot bijvoorbeeld' + +loop_ +_cat_3.id +_cat_3.name +_cat_3.num +1 aap 1 +2 aap 2 + )"; + + using namespace cif::literals; + + struct data_membuf : public std::streambuf + { + data_membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } data_buffer(const_cast(data), sizeof(data) - 1); + + std::istream is_data(&data_buffer); + f.load(is_data, validator); + + auto &cat1 = f.front()["cat_1"]; + auto &cat2 = f.front()["cat_2"]; + auto &cat3 = f.front()["cat_3"]; + + // some parent/child tests + + // find all children in cat2 for the row with id == 1 in cat1 + auto rs1 = cat1.get_children(cat1.find1("id"_key == 1), cat2); + CHECK(rs1.size() == 2); + + auto rs2 = cat1.get_children(cat1.find1("id"_key == 2), cat2); + CHECK(rs2.size() == 1); + + auto rs3 = cat1.get_children(cat1.find1("id"_key == 3), cat2); + CHECK(rs3.size() == 0); + + // finding parents + auto rs4 = cat2.get_parents(cat2.find1("id"_key == 1), cat1); + CHECK(rs4.size() == 1); + + auto rs5 = cat3.get_parents(cat3.find1("id"_key == 1), cat2); + CHECK(rs5.size() == 1); + + // This link is not defined: + auto rs6 = cat3.get_parents(cat3.find1("id"_key == 1), cat1); + CHECK(rs6.size() == 0); +} + +// -------------------------------------------------------------------- + +// TEST_CASE("bondmap_1") // { -// const char dict[] = R"( -// data_test_dict.dic -// _datablock.id test_dict.dic -// _datablock.description -// ; -// A test dictionary -// ; -// _dictionary.title test_dict.dic -// _dictionary.datablock_id test_dict.dic -// _dictionary.version 1.0 +// cif::VERBOSE = 2; -// loop_ -// _item_type_list.code -// _item_type_list.primitive_code -// _item_type_list.construct -// _item_type_list.detail -// code char -// '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' -// ; code item types/single words ... -// ; -// text char -// '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' -// ; text item types / multi-line text ... -// ; -// int numb -// '[+-]?[0-9]+' -// ; int item types are the subset of numbers that are the negative -// or positive integers. -// ; - -// save_cat_1 -// _category.description 'A simple test category' -// _category.id cat_1 -// _category.mandatory_code no -// _category_key.name '_cat_1.id' - -// save_ - -// save__cat_1.id -// _item.name '_cat_1.id' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_aliases.dictionary cif_core.dic -// _item_aliases.version 2.0.1 -// _item_linked.child_name '_cat_2.parent_id' -// _item_linked.parent_name '_cat_1.id' -// _item_type.code code -// save_ - -// save__cat_1.name -// _item.name '_cat_1.name' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_aliases.dictionary cif_core.dic -// _item_aliases.version 2.0.1 -// _item_type.code text -// save_ - -// save_cat_2 -// _category.description 'A second simple test category' -// _category.id cat_2 -// _category.mandatory_code no -// _category_key.name '_cat_2.id' -// save_ - -// save__cat_2.id -// _item.name '_cat_2.id' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_aliases.dictionary cif_core.dic -// _item_aliases.version 2.0.1 -// _item_type.code int -// save_ - -// save__cat_2.parent_id -// _item.name '_cat_2.parent_id' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_aliases.dictionary cif_core.dic -// _item_aliases.version 2.0.1 -// _item_type.code code -// save_ - -// save__cat_2.desc -// _item.name '_cat_2.desc' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_aliases.dictionary cif_core.dic -// _item_aliases.version 2.0.1 -// _item_type.code text -// save_ -// )"; - -// struct membuf : public std::streambuf -// { -// membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } buffer(const_cast(dict), sizeof(dict) - 1); - -// std::istream is_dict(&buffer); - -// cif::validator validator(is_dict); - -// cif::file f; - -// // -------------------------------------------------------------------- - -// const char data[] = R"( -// data_test +// // sections taken from CCD compounds.cif +// auto components = R"( +// data_ASN // loop_ -// _cat_1.id -// _cat_1.name -// 1 Aap -// 2 Noot -// 3 Mies - +// _chem_comp_bond.comp_id +// _chem_comp_bond.atom_id_1 +// _chem_comp_bond.atom_id_2 +// _chem_comp_bond.value_order +// _chem_comp_bond.pdbx_aromatic_flag +// _chem_comp_bond.pdbx_stereo_config +// _chem_comp_bond.pdbx_ordinal +// ASN N CA SING N N 1 +// ASN N H SING N N 2 +// ASN N H2 SING N N 3 +// ASN CA C SING N N 4 +// ASN CA CB SING N N 5 +// ASN CA HA SING N N 6 +// ASN C O DOUB N N 7 +// ASN C OXT SING N N 8 +// ASN CB CG SING N N 9 +// ASN CB HB2 SING N N 10 +// ASN CB HB3 SING N N 11 +// ASN CG OD1 DOUB N N 12 +// ASN CG ND2 SING N N 13 +// ASN ND2 HD21 SING N N 14 +// ASN ND2 HD22 SING N N 15 +// ASN OXT HXT SING N N 16 +// data_PHE // loop_ -// _cat_2.id -// _cat_2.parent_id -// _cat_2.desc -// 1 1 'Een dier' -// 2 1 'Een andere aap' -// 3 2 'walnoot bijvoorbeeld' -// )"; - -// struct data_membuf : public std::streambuf -// { -// data_membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } data_buffer(const_cast(data), sizeof(data) - 1); - -// std::istream is_data(&data_buffer); -// f.load(is_data, validator); - -// SECTION("one") -// { -// auto &cat1 = f.front()["cat_1"]; -// auto &cat2 = f.front()["cat_2"]; - -// CHECK(cat1.size() == 3); -// CHECK(cat2.size() == 3); - -// cat1.erase(cif::key("id") == 1); - -// CHECK(cat1.size() == 2); -// CHECK(cat2.size() == 1); - -// // CHECK_THROWS_AS(cat2.emplace({ -// // { "id", 4 }, -// // { "parent_id", 4 }, -// // { "desc", "moet fout gaan" } -// // }), std::exception); - -// CHECK_THROWS_AS(cat2.emplace({ { "id", "vijf" }, // <- invalid value -// { "parent_id", 2 }, -// { "desc", "moet fout gaan" } }), -// std::exception); -// } - -// // SECTION("two") -// // { -// // auto &cat1 = f.front()["cat_1"]; -// // auto &cat2 = f.front()["cat_2"]; - -// // cat1.update_value(cif::all(), "id", [](std::string_view v) -> std::string -// // { -// // int vi; -// // auto [ec, ptr] = std::from_chars(v.data(), v.data() + v.length(), vi); -// // return std::to_string(vi + 1); -// // }); - -// // CHECK(cat1.find1(cif::key("id") == 2, "name") == "Aap"); -// // CHECK(cat1.find1(cif::key("id") == 3, "name") == "Noot"); -// // CHECK(cat1.find1(cif::key("id") == 4, "name") == "Mies"); - -// // CHECK(cat2.find1(cif::key("id") == 1, "parent_id") == 2); -// // CHECK(cat2.find1(cif::key("id") == 2, "parent_id") == 2); -// // CHECK(cat2.find1(cif::key("id") == 3, "parent_id") == 3); -// // } -// } - -// // -------------------------------------------------------------------- - -// TEST_CASE("d2") -// { -// const char dict[] = R"( -// data_test_dict.dic -// _datablock.id test_dict.dic -// _datablock.description -// ; -// A test dictionary -// ; -// _dictionary.title test_dict.dic -// _dictionary.datablock_id test_dict.dic -// _dictionary.version 1.0 - -// loop_ -// _item_type_list.code -// _item_type_list.primitive_code -// _item_type_list.construct -// _item_type_list.detail -// code char -// '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' -// ; code item types/single words ... -// ; -// ucode uchar -// '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' -// ; code item types/single words, case insensitive -// ; -// text char -// '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' -// ; text item types / multi-line text ... -// ; -// int numb -// '[+-]?[0-9]+' -// ; int item types are the subset of numbers that are the negative -// or positive integers. -// ; - -// save_cat_1 -// _category.description 'A simple test category' -// _category.id cat_1 -// _category.mandatory_code no -// _category_key.name '_cat_1.id' -// save_ - -// save__cat_1.id -// _item.name '_cat_1.id' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_type.code code -// save_ - -// save__cat_1.c -// _item.name '_cat_1.c' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_type.code ucode -// save_ -// )"; - -// struct membuf : public std::streambuf -// { -// membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } buffer(const_cast(dict), sizeof(dict) - 1); - -// std::istream is_dict(&buffer); - -// cif::validator validator(is_dict); - -// cif::file f; - -// // -------------------------------------------------------------------- - -// const char data[] = R"( -// data_test +// _chem_comp_bond.comp_id +// _chem_comp_bond.atom_id_1 +// _chem_comp_bond.atom_id_2 +// _chem_comp_bond.value_order +// _chem_comp_bond.pdbx_aromatic_flag +// _chem_comp_bond.pdbx_stereo_config +// _chem_comp_bond.pdbx_ordinal +// PHE N CA SING N N 1 +// PHE N H SING N N 2 +// PHE N H2 SING N N 3 +// PHE CA C SING N N 4 +// PHE CA CB SING N N 5 +// PHE CA HA SING N N 6 +// PHE C O DOUB N N 7 +// PHE C OXT SING N N 8 +// PHE CB CG SING N N 9 +// PHE CB HB2 SING N N 10 +// PHE CB HB3 SING N N 11 +// PHE CG CD1 DOUB Y N 12 +// PHE CG CD2 SING Y N 13 +// PHE CD1 CE1 SING Y N 14 +// PHE CD1 HD1 SING N N 15 +// PHE CD2 CE2 DOUB Y N 16 +// PHE CD2 HD2 SING N N 17 +// PHE CE1 CZ DOUB Y N 18 +// PHE CE1 HE1 SING N N 19 +// PHE CE2 CZ SING Y N 20 +// PHE CE2 HE2 SING N N 21 +// PHE CZ HZ SING N N 22 +// PHE OXT HXT SING N N 23 +// data_PRO // loop_ -// _cat_1.id -// _cat_1.c -// aap Aap -// noot Noot -// mies Mies -// )"; - -// struct data_membuf : public std::streambuf -// { -// data_membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } data_buffer(const_cast(data), sizeof(data) - 1); - -// std::istream is_data(&data_buffer); -// f.load(is_data, validator); - -// auto &cat1 = f.front()["cat_1"]; - -// CHECK(cat1.size() == 3); - -// cat1.erase(cif::key("id") == "AAP"); - -// CHECK(cat1.size() == 3); - -// cat1.erase(cif::key("id") == "noot"); - -// CHECK(cat1.size() == 2); - -// // should fail with duplicate key: -// CHECK_THROWS_AS(cat1.emplace({ { "id", "aap" }, -// { "c", "2e-aap" } }), -// std::exception); - -// cat1.erase(cif::key("id") == "aap"); - -// CHECK(cat1.size() == 1); - -// cat1.emplace({ { "id", "aap" }, -// { "c", "2e-aap" } }); - -// CHECK(cat1.size() == 2); -// } - -// // -------------------------------------------------------------------- - -// TEST_CASE("d3") -// { -// const char dict[] = R"( -// data_test_dict.dic -// _datablock.id test_dict.dic -// _datablock.description -// ; -// A test dictionary -// ; -// _dictionary.title test_dict.dic -// _dictionary.datablock_id test_dict.dic -// _dictionary.version 1.0 - -// loop_ -// _item_type_list.code -// _item_type_list.primitive_code -// _item_type_list.construct -// code char -// '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' - -// text char -// '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' - -// int numb -// '[+-]?[0-9]+' - -// save_cat_1 -// _category.description 'A simple test category' -// _category.id cat_1 -// _category.mandatory_code no -// _category_key.name '_cat_1.id' - -// save_ - -// save__cat_1.id -// _item.name '_cat_1.id' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_linked.child_name '_cat_2.parent_id' -// _item_linked.parent_name '_cat_1.id' -// _item_type.code code -// save_ - -// save__cat_1.name1 -// _item.name '_cat_1.name1' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_type.code text -// save_ - -// save__cat_1.name2 -// _item.name '_cat_1.name2' -// _item.category_id cat_1 -// _item.mandatory_code no -// _item_linked.child_name '_cat_2.name2' -// _item_linked.parent_name '_cat_1.name2' -// _item_type.code text -// save_ - -// save_cat_2 -// _category.description 'A second simple test category' -// _category.id cat_2 -// _category.mandatory_code no -// _category_key.name '_cat_2.id' -// save_ - -// save__cat_2.id -// _item.name '_cat_2.id' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_2.parent_id -// _item.name '_cat_2.parent_id' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code code -// save_ - -// save__cat_2.name2 -// _item.name '_cat_2.name2' -// _item.category_id cat_2 -// _item.mandatory_code no -// _item_type.code text -// save_ - -// save__cat_2.desc -// _item.name '_cat_2.desc' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code text -// save_ -// )"; - -// struct membuf : public std::streambuf -// { -// membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } buffer(const_cast(dict), sizeof(dict) - 1); - -// std::istream is_dict(&buffer); - -// cif::validator validator(is_dict); - -// cif::file f; - -// // -------------------------------------------------------------------- - -// const char data[] = R"( -// data_test -// loop_ -// _cat_1.id -// _cat_1.name1 -// _cat_1.name2 -// 1 Aap aap -// 2 Noot noot -// 3 Mies mies - -// loop_ -// _cat_2.id -// _cat_2.parent_id -// _cat_2.name2 -// _cat_2.desc -// 1 1 aap 'Een dier' -// 2 1 . 'Een andere aap' -// 3 2 noot 'walnoot bijvoorbeeld' -// 4 2 n2 hazelnoot -// )"; - -// struct data_membuf : public std::streambuf -// { -// data_membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } data_buffer(const_cast(data), sizeof(data) - 1); - -// std::istream is_data(&data_buffer); -// f.load(is_data, validator); - -// auto &cat1 = f.front()["cat_1"]; -// auto &cat2 = f.front()["cat_2"]; - -// // check a rename in parent and child - -// for (auto r : cat1.find(cif::key("id") == 1)) -// { -// r["id"] = 10; -// break; -// } - -// CHECK(cat1.size() == 3); -// CHECK(cat2.size() == 4); - -// CHECK(cat1.find(cif::key("id") == 1).size() == 0); -// CHECK(cat1.find(cif::key("id") == 10).size() == 1); - -// CHECK(cat2.find(cif::key("parent_id") == 1).size() == 0); -// CHECK(cat2.find(cif::key("parent_id") == 10).size() == 2); - -// // check a rename in parent and child, this time only one child should be renamed - -// for (auto r : cat1.find(cif::key("id") == 2)) -// { -// r["id"] = 20; -// break; -// } - -// CHECK(cat1.size() == 3); -// CHECK(cat2.size() == 4); - -// CHECK(cat1.find(cif::key("id") == 2).size() == 0); -// CHECK(cat1.find(cif::key("id") == 20).size() == 1); - -// CHECK(cat2.find(cif::key("parent_id") == 2).size() == 1); -// CHECK(cat2.find(cif::key("parent_id") == 20).size() == 1); - -// CHECK(cat2.find(cif::key("parent_id") == 2 and cif::key("name2") == "noot").size() == 0); -// CHECK(cat2.find(cif::key("parent_id") == 2 and cif::key("name2") == "n2").size() == 1); -// CHECK(cat2.find(cif::key("parent_id") == 20 and cif::key("name2") == "noot").size() == 1); -// CHECK(cat2.find(cif::key("parent_id") == 20 and cif::key("name2") == "n2").size() == 0); - -// // -------------------------------------------------------------------- - -// cat1.erase(cif::key("id") == 10); - -// CHECK(cat1.size() == 2); -// CHECK(cat2.size() == 2); // TODO: Is this really what we want? - -// cat1.erase(cif::key("id") == 20); - -// CHECK(cat1.size() == 1); -// CHECK(cat2.size() == 1); // TODO: Is this really what we want? -// } - -// // -------------------------------------------------------------------- - -// TEST_CASE("d4") -// { -// const char dict[] = R"( -// data_test_dict.dic -// _datablock.id test_dict.dic -// _datablock.description -// ; -// A test dictionary -// ; -// _dictionary.title test_dict.dic -// _dictionary.datablock_id test_dict.dic -// _dictionary.version 1.0 - -// loop_ -// _item_type_list.code -// _item_type_list.primitive_code -// _item_type_list.construct -// code char -// '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' - -// text char -// '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' - -// int numb -// '[+-]?[0-9]+' - -// save_cat_1 -// _category.description 'A simple test category' -// _category.id cat_1 -// _category.mandatory_code no -// _category_key.name '_cat_1.id' -// save_ - -// save__cat_1.id -// _item.name '_cat_1.id' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_linked.child_name '_cat_2.parent_id' -// _item_linked.parent_name '_cat_1.id' -// _item_type.code int -// save_ - -// save__cat_1.id2 -// _item.name '_cat_1.id2' -// _item.category_id cat_1 -// _item.mandatory_code no -// _item_linked.child_name '_cat_2.parent_id2' -// _item_linked.parent_name '_cat_1.id2' -// _item_type.code code -// save_ - -// save__cat_1.id3 -// _item.name '_cat_1.id3' -// _item.category_id cat_1 -// _item.mandatory_code no -// _item_linked.child_name '_cat_2.parent_id3' -// _item_linked.parent_name '_cat_1.id3' -// _item_type.code text -// save_ - -// save_cat_2 -// _category.description 'A second simple test category' -// _category.id cat_2 -// _category.mandatory_code no -// _category_key.name '_cat_2.id' -// save_ - -// save__cat_2.id -// _item.name '_cat_2.id' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_2.parent_id -// _item.name '_cat_2.parent_id' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_2.parent_id2 -// _item.name '_cat_2.parent_id2' -// _item.category_id cat_2 -// _item.mandatory_code no -// _item_type.code code -// save_ - -// save__cat_2.parent_id3 -// _item.name '_cat_2.parent_id3' -// _item.category_id cat_2 -// _item.mandatory_code no -// _item_type.code code -// save_ - -// )"; - -// struct membuf : public std::streambuf -// { -// membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } buffer(const_cast(dict), sizeof(dict) - 1); - -// std::istream is_dict(&buffer); - -// cif::validator validator(is_dict); - -// cif::file f; - -// // -------------------------------------------------------------------- - -// const char data[] = R"( -// data_test -// loop_ -// _cat_1.id -// _cat_1.id2 -// _cat_1.id3 -// 1 aap aap -// 2 . noot -// 3 mies . -// 4 . . - -// loop_ -// _cat_2.id -// _cat_2.parent_id -// _cat_2.parent_id2 -// _cat_2.parent_id3 -// 1 1 aap aap -// 2 1 . x -// 3 1 aap . -// 4 2 noot noot -// 5 2 . noot -// 6 2 noot . -// 7 2 . . -// 8 3 mies mies -// 9 3 . mies -// 10 3 mies . -// 11 4 roos roos -// 12 4 . roos -// 13 4 roos . -// )"; - -// struct data_membuf : public std::streambuf -// { -// data_membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } data_buffer(const_cast(data), sizeof(data) - 1); - -// std::istream is_data(&data_buffer); -// f.load(is_data, validator); - -// auto &cat1 = f.front()["cat_1"]; -// auto &cat2 = f.front()["cat_2"]; - -// // check a rename in parent and child - -// for (auto r : cat1.find(cif::key("id") == 1)) -// { -// r["id"] = 10; -// break; -// } - -// CHECK(cat1.size() == 4); -// CHECK(cat2.size() == 13); - -// CHECK(cat1.find(cif::key("id") == 1).size() == 0); -// CHECK(cat1.find(cif::key("id") == 10).size() == 1); - -// CHECK(cat2.find(cif::key("parent_id") == 1).size() == 1); -// CHECK(cat2.find(cif::key("parent_id") == 10).size() == 2); - -// for (auto r : cat1.find(cif::key("id") == 2)) -// { -// r["id"] = 20; -// break; -// } - -// CHECK(cat1.size() == 4); -// CHECK(cat2.size() == 13); - -// CHECK(cat1.find(cif::key("id") == 2).size() == 0); -// CHECK(cat1.find(cif::key("id") == 20).size() == 1); - -// CHECK(cat2.find(cif::key("parent_id") == 2).size() == 2); -// CHECK(cat2.find(cif::key("parent_id") == 20).size() == 2); - -// for (auto r : cat1.find(cif::key("id") == 3)) -// { -// r["id"] = 30; -// break; -// } - -// CHECK(cat1.size() == 4); -// CHECK(cat2.size() == 13); - -// CHECK(cat1.find(cif::key("id") == 3).size() == 0); -// CHECK(cat1.find(cif::key("id") == 30).size() == 1); - -// CHECK(cat2.find(cif::key("parent_id") == 3).size() == 2); -// CHECK(cat2.find(cif::key("parent_id") == 30).size() == 1); - -// for (auto r : cat1.find(cif::key("id") == 4)) -// { -// r["id"] = 40; -// break; -// } - -// CHECK(cat1.size() == 4); -// CHECK(cat2.size() == 13); - -// CHECK(cat1.find(cif::key("id") == 4).size() == 0); -// CHECK(cat1.find(cif::key("id") == 10).size() == 1); - -// CHECK(cat2.find(cif::key("parent_id") == 4).size() == 3); -// CHECK(cat2.find(cif::key("parent_id") == 40).size() == 0); -// } - -// // -------------------------------------------------------------------- - -// TEST_CASE("d5") -// { -// const char dict[] = R"( -// data_test_dict.dic -// _datablock.id test_dict.dic -// _datablock.description -// ; -// A test dictionary -// ; -// _dictionary.title test_dict.dic -// _dictionary.datablock_id test_dict.dic -// _dictionary.version 1.0 - -// loop_ -// _item_type_list.code -// _item_type_list.primitive_code -// _item_type_list.construct -// code char -// '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' - -// text char -// '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' - -// int numb -// '[+-]?[0-9]+' - -// save_cat_1 -// _category.description 'A simple test category' -// _category.id cat_1 -// _category.mandatory_code no -// _category_key.name '_cat_1.id' -// save_ - -// save__cat_1.id -// _item.name '_cat_1.id' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save_cat_2 -// _category.description 'A second simple test category' -// _category.id cat_2 -// _category.mandatory_code no -// _category_key.name '_cat_2.id' -// save_ - -// save__cat_2.id -// _item.name '_cat_2.id' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_2.parent_id -// _item.name '_cat_2.parent_id' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_2.parent_id2 -// _item.name '_cat_2.parent_id2' -// _item.category_id cat_2 -// _item.mandatory_code no -// _item_type.code code -// save_ - -// save__cat_2.parent_id3 -// _item.name '_cat_2.parent_id3' -// _item.category_id cat_2 -// _item.mandatory_code no -// _item_type.code code -// save_ - -// loop_ -// _pdbx_item_linked_group_list.child_category_id -// _pdbx_item_linked_group_list.link_group_id -// _pdbx_item_linked_group_list.child_name -// _pdbx_item_linked_group_list.parent_name -// _pdbx_item_linked_group_list.parent_category_id -// cat_2 1 '_cat_2.parent_id' '_cat_1.id' cat_1 -// cat_2 2 '_cat_2.parent_id2' '_cat_1.id' cat_1 -// cat_2 3 '_cat_2.parent_id3' '_cat_1.id' cat_1 - -// loop_ -// _pdbx_item_linked_group.category_id -// _pdbx_item_linked_group.link_group_id -// _pdbx_item_linked_group.label -// cat_2 1 cat_2:cat_1:1 -// cat_2 2 cat_2:cat_1:2 -// cat_2 3 cat_2:cat_1:3 -// )"; - -// struct membuf : public std::streambuf -// { -// membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } buffer(const_cast(dict), sizeof(dict) - 1); - -// std::istream is_dict(&buffer); - -// cif::validator validator(is_dict); - -// cif::file f; - -// // -------------------------------------------------------------------- - -// const char data[] = R"( -// data_test -// loop_ -// _cat_1.id -// 1 -// 2 -// 3 - -// loop_ -// _cat_2.id -// _cat_2.parent_id -// _cat_2.parent_id2 -// _cat_2.parent_id3 -// 1 1 ? ? -// 2 ? 1 ? -// 3 ? ? 1 -// 4 2 2 ? -// 5 2 ? 2 -// 6 ? 2 2 -// 7 3 3 3 -// )"; - -// // -------------------------------------------------------------------- - -// struct data_membuf : public std::streambuf -// { -// data_membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } data_buffer(const_cast(data), sizeof(data) - 1); - -// std::istream is_data(&data_buffer); -// f.load(is_data, validator); - -// auto &cat1 = f.front()["cat_1"]; -// auto &cat2 = f.front()["cat_2"]; - -// // -------------------------------------------------------------------- -// // check iterate children - -// auto PR2set = cat1.find(cif::key("id") == 2); -// CHECK(PR2set.size() == 1); -// auto PR2 = PR2set.front(); -// CHECK(PR2["id"].as() == 2); - -// auto CR2set = cat1.get_children(PR2, cat2); -// CHECK(CR2set.size() == 3); -// CHECK(CR2set.size() == 3); - -// std::vector CRids; -// std::transform(CR2set.begin(), CR2set.end(), std::back_inserter(CRids), [](cif::row_handle r) -// { return r["id"].as(); }); -// std::sort(CRids.begin(), CRids.end()); -// CHECK(CRids == std::vector({ 4, 5, 6 })); - -// // check a rename in parent and child - -// for (auto r : cat1.find(cif::key("id") == 1)) -// { -// r["id"] = 10; -// break; -// } - -// CHECK(cat1.size() == 3); -// CHECK(cat2.size() == 7); - -// CHECK(cat1.find(cif::key("id") == 1).size() == 0); -// CHECK(cat1.find(cif::key("id") == 10).size() == 1); - -// CHECK(cat2.find(cif::key("parent_id") == 1).size() == 0); -// CHECK(cat2.find(cif::key("parent_id2") == 1).size() == 0); -// CHECK(cat2.find(cif::key("parent_id3") == 1).size() == 0); -// CHECK(cat2.find(cif::key("parent_id") == 10).size() == 1); -// CHECK(cat2.find(cif::key("parent_id2") == 10).size() == 1); -// CHECK(cat2.find(cif::key("parent_id3") == 10).size() == 1); - -// for (auto r : cat1.find(cif::key("id") == 2)) -// { -// r["id"] = 20; -// break; -// } - -// CHECK(cat1.size() == 3); -// CHECK(cat2.size() == 7); - -// CHECK(cat1.find(cif::key("id") == 2).size() == 0); -// CHECK(cat1.find(cif::key("id") == 20).size() == 1); - -// CHECK(cat2.find(cif::key("parent_id") == 2).size() == 0); -// CHECK(cat2.find(cif::key("parent_id2") == 2).size() == 0); -// CHECK(cat2.find(cif::key("parent_id3") == 2).size() == 0); -// CHECK(cat2.find(cif::key("parent_id") == 20).size() == 2); -// CHECK(cat2.find(cif::key("parent_id2") == 20).size() == 2); -// CHECK(cat2.find(cif::key("parent_id3") == 20).size() == 2); - -// for (auto r : cat1.find(cif::key("id") == 3)) -// { -// r["id"] = 30; -// break; -// } - -// CHECK(cat1.size() == 3); -// CHECK(cat2.size() == 7); - -// CHECK(cat1.find(cif::key("id") == 3).size() == 0); -// CHECK(cat1.find(cif::key("id") == 30).size() == 1); - -// CHECK(cat2.find(cif::key("parent_id") == 3).size() == 0); -// CHECK(cat2.find(cif::key("parent_id2") == 3).size() == 0); -// CHECK(cat2.find(cif::key("parent_id3") == 3).size() == 0); -// CHECK(cat2.find(cif::key("parent_id") == 30).size() == 1); -// CHECK(cat2.find(cif::key("parent_id2") == 30).size() == 1); -// CHECK(cat2.find(cif::key("parent_id3") == 30).size() == 1); - -// // test delete - -// cat1.erase(cif::key("id") == 10); -// CHECK(cat1.size() == 2); -// CHECK(cat2.size() == 4); - -// cat1.erase(cif::key("id") == 20); -// CHECK(cat1.size() == 1); -// CHECK(cat2.size() == 1); - -// cat1.erase(cif::key("id") == 30); -// CHECK(cat1.size() == 0); -// CHECK(cat2.size() == 0); -// } - -// // -------------------------------------------------------------------- - -// TEST_CASE("d6") -// { -// const char dict[] = R"( -// data_test_dict.dic -// _datablock.id test_dict.dic -// _datablock.description -// ; -// A test dictionary -// ; -// _dictionary.title test_dict.dic -// _dictionary.datablock_id test_dict.dic -// _dictionary.version 1.0 - -// loop_ -// _item_type_list.code -// _item_type_list.primitive_code -// _item_type_list.construct -// code char -// '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' - -// text char -// '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' - -// int numb -// '[+-]?[0-9]+' - -// save_cat_1 -// _category.description 'A simple test category' -// _category.id cat_1 -// _category.mandatory_code yes -// _category_key.name '_cat_1.id' -// save_ - -// save__cat_1.id -// _item.name '_cat_1.id' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_1.id_2 -// _item.name '_cat_1.id_2' -// _item.category_id cat_1 -// _item.mandatory_code no -// _item_type.code int -// save_ - -// save_cat_2 -// _category.description 'A second simple test category' -// _category.id cat_2 -// _category.mandatory_code no -// _category_key.name '_cat_2.id' -// save_ - -// save__cat_2.id -// _item.name '_cat_2.id' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_2.parent_id -// _item.name '_cat_2.parent_id' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_2.parent_id_2 -// _item.name '_cat_2.parent_id_2' -// _item.category_id cat_2 -// _item.mandatory_code no -// _item_type.code code -// save_ - -// loop_ -// _pdbx_item_linked_group_list.child_category_id -// _pdbx_item_linked_group_list.link_group_id -// _pdbx_item_linked_group_list.child_name -// _pdbx_item_linked_group_list.parent_name -// _pdbx_item_linked_group_list.parent_category_id -// cat_2 1 '_cat_2.parent_id' '_cat_1.id' cat_1 -// cat_2 1 '_cat_2.parent_id_2' '_cat_1.id_2' cat_1 - -// loop_ -// _pdbx_item_linked_group.category_id -// _pdbx_item_linked_group.link_group_id -// _pdbx_item_linked_group.label -// cat_2 1 cat_2:cat_1:1 -// )"; - -// struct membuf : public std::streambuf -// { -// membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } buffer(const_cast(dict), sizeof(dict) - 1); - -// std::istream is_dict(&buffer); - -// cif::validator validator(is_dict); - -// cif::file f; - -// // -------------------------------------------------------------------- - -// const char data[] = R"( -// data_test -// loop_ -// _cat_1.id -// _cat_1.id_2 -// 1 1 -// 2 2 -// 3 ? - -// loop_ -// _cat_2.id -// _cat_2.parent_id -// _cat_2.parent_id_2 -// 0 1 1 -// 1 1 ? -// 2 ? 1 -// 3 ? ? -// 4 2 2 -// 5 3 1 -// 6 3 ? -// )"; - -// // -------------------------------------------------------------------- - -// struct data_membuf : public std::streambuf -// { -// data_membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } data_buffer(const_cast(data), sizeof(data) - 1); - -// std::istream is_data(&data_buffer); -// f.load(is_data, validator); - -// // auto &cat1 = f.front()["cat_1"]; -// auto &cat2 = f.front()["cat_2"]; - -// // f.front().validate_links(); -// // CHECK(not ); - -// using namespace cif::literals; - -// CHECK( cat2.has_parents(cat2.find1("id"_key == 0))); -// CHECK( cat2.has_parents(cat2.find1("id"_key == 1))); -// CHECK( cat2.has_parents(cat2.find1("id"_key == 2))); -// CHECK(not cat2.has_parents(cat2.find1("id"_key == 3))); -// CHECK( cat2.has_parents(cat2.find1("id"_key == 4))); -// CHECK(not cat2.has_parents(cat2.find1("id"_key == 5))); -// CHECK( cat2.has_parents(cat2.find1("id"_key == 6))); -// } - -// // -------------------------------------------------------------------- - -// TEST_CASE("c1") -// { -// cif::VERBOSE = 1; - -// auto f = R"(data_TEST -// # -// loop_ -// _test.id -// _test.name -// 1 aap -// 2 noot -// 3 mies -// 4 . -// 5 ? -// )"_cf; - -// auto &db = f.front(); - -// for (auto r : db["test"].find(cif::key("id") == 1)) -// { -// const auto &[id, name] = r.get("id", "name"); -// CHECK(id == 1); -// CHECK(name == "aap"); -// } - -// for (auto r : db["test"].find(cif::key("id") == 4)) -// { -// const auto &[id, name] = r.get("id", "name"); -// CHECK(id == 4); -// CHECK(name.empty()); -// } - -// for (auto r : db["test"].find(cif::key("id") == 5)) -// { -// const auto &[id, name] = r.get("id", "name"); -// CHECK(id == 5); -// CHECK(name.empty()); -// } - -// // optional - -// for (auto r : db["test"]) -// { -// const auto &[id, name] = r.get>("id", "name"); -// switch (id) -// { -// case 1: CHECK(name == "aap"); break; -// case 2: CHECK(name == "noot"); break; -// case 3: CHECK(name == "mies"); break; -// case 4: -// case 5: CHECK(not name); break; -// default: -// CHECK(false); -// } -// } -// } - -// TEST_CASE("c2") -// { -// cif::VERBOSE = 1; - -// auto f = R"(data_TEST -// # -// loop_ -// _test.id -// _test.name -// 1 aap -// 2 noot -// 3 mies -// 4 . -// 5 ? -// )"_cf; - -// auto &db = f.front(); - -// // query tests - -// for (const auto &[id, name] : db["test"].rows>("id", "name")) -// { -// switch (id) -// { -// case 1: CHECK(name == "aap"); break; -// case 2: CHECK(name == "noot"); break; -// case 3: CHECK(name == "mies"); break; -// case 4: -// case 5: CHECK(not name); break; -// default: -// CHECK(false); -// } -// } -// } - -// TEST_CASE("c3") -// { -// auto f = R"(data_TEST -// # -// loop_ -// _test.id -// _test.name -// 1 aap -// 2 noot -// 3 mies -// 4 . -// 5 ? -// )"_cf; - -// auto &db = f.front(); - -// // query tests -// for (const auto &[id, name] : db["test"].find>(cif::all(), "id", "name")) -// { -// switch (id) -// { -// case 1: CHECK(name == "aap"); break; -// case 2: CHECK(name == "noot"); break; -// case 3: CHECK(name == "mies"); break; -// case 4: -// case 5: CHECK(not name); break; -// default: -// CHECK(false); -// } -// } - -// const auto &[id, name] = db["test"].find1(cif::key("id") == 1, "id", "name"); - -// CHECK(id == 1); -// CHECK(name == "aap"); -// } - -// TEST_CASE("c4") -// { -// auto f = R"(data_TEST -// # -// loop_ -// _test.id -// _test.name -// 1 aap -// 2 noot -// 3 mies -// 4 . -// 5 ? -// )"_cf; - -// auto &db = f.front(); - -// // query tests -// CHECK(db["test"].find_max("id") == 5); -// CHECK(db["test"].find_max("id", cif::key("name") != cif::null) == 3); - -// CHECK(db["test"].find_min("id") == 1); -// CHECK(db["test"].find_min("id", cif::key("name") == cif::null) == 4); - -// // count tests -// CHECK(db["test"].count(cif::all()) == 5); -// CHECK(db["test"].count(cif::key("name") != cif::null) == 3); -// CHECK(db["test"].count(cif::key("name") == cif::null) == 2); - -// // find_first tests -// CHECK(db["test"].find_first(cif::key("id") == 1, "id") == 1); -// CHECK(db["test"].find_first(cif::all(), "id") == 1); - -// std::optional v; - -// v = db["test"].find_first>(cif::key("id") == 1, "id"); -// CHECK(v.has_value()); -// CHECK(*v == 1); - -// v = db["test"].find_first>(cif::key("id") == 6, "id"); -// CHECK(not v.has_value()); - -// // find1 tests -// CHECK(db["test"].find1(cif::key("id") == 1, "id") == 1); -// CHECK_THROWS_AS(db["test"].find1(cif::all(), "id"), cif::multiple_results_error); -// } - -// // -------------------------------------------------------------------- -// // rename test - -// TEST_CASE("r1") -// { -// /* -// Rationale: - -// The pdbx_mmcif dictionary contains inconsistent child-parent relations. E.g. atom_site is parent -// of pdbx_nonpoly_scheme which itself is a parent of pdbx_entity_nonpoly. If I want to rename a residue -// I cannot update pdbx_nonpoly_scheme since changing a parent changes children, but not vice versa. - -// But if I change the comp_id in atom_site, the pdbx_nonpoly_scheme is updated, that's good, and then -// pdbx_entity_nonpoly is updated and that's bad. - -// The idea is now that if we update a parent and a child that must change as well, we first check -// if there are more parents of this child that will not change. In that case we have to split the -// child into two, one with the new value and one with the old. We then of course have to split all -// children of this split row that are direct children. -// */ - -// const char dict[] = R"( -// data_test_dict.dic -// _datablock.id test_dict.dic -// _datablock.description -// ; -// A test dictionary -// ; -// _dictionary.title test_dict.dic -// _dictionary.datablock_id test_dict.dic -// _dictionary.version 1.0 - -// loop_ -// _item_type_list.code -// _item_type_list.primitive_code -// _item_type_list.construct -// code char -// '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' - -// text char -// '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' - -// int numb -// '[+-]?[0-9]+' - -// save_cat_1 -// _category.description 'A simple test category' -// _category.id cat_1 -// _category.mandatory_code no -// _category_key.name '_cat_1.id' -// save_ - -// save__cat_1.id -// _item.name '_cat_1.id' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_linked.child_name '_cat_2.parent_id' -// _item_linked.parent_name '_cat_1.id' -// _item_type.code int -// save_ - -// save__cat_1.name -// _item.name '_cat_1.name' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_type.code code -// save_ - -// save__cat_1.desc -// _item.name '_cat_1.desc' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_type.code text -// save_ - -// save_cat_2 -// _category.description 'A second simple test category' -// _category.id cat_2 -// _category.mandatory_code no -// _category_key.name '_cat_2.id' -// save_ - -// save__cat_2.id -// _item.name '_cat_2.id' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_2.name -// _item.name '_cat_2.name' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code code -// save_ - -// save__cat_2.num -// _item.name '_cat_2.num' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_2.desc -// _item.name '_cat_2.desc' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code text -// save_ - -// save_cat_3 -// _category.description 'A third simple test category' -// _category.id cat_3 -// _category.mandatory_code no -// _category_key.name '_cat_3.id' -// save_ - -// save__cat_3.id -// _item.name '_cat_3.id' -// _item.category_id cat_3 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_3.name -// _item.name '_cat_3.name' -// _item.category_id cat_3 -// _item.mandatory_code yes -// _item_type.code code -// save_ - -// save__cat_3.num -// _item.name '_cat_3.num' -// _item.category_id cat_3 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// loop_ -// _pdbx_item_linked_group_list.child_category_id -// _pdbx_item_linked_group_list.link_group_id -// _pdbx_item_linked_group_list.child_name -// _pdbx_item_linked_group_list.parent_name -// _pdbx_item_linked_group_list.parent_category_id -// cat_1 1 '_cat_1.name' '_cat_2.name' cat_2 -// cat_2 1 '_cat_2.name' '_cat_3.name' cat_3 -// cat_2 1 '_cat_2.num' '_cat_3.num' cat_3 - -// )"; - -// struct membuf : public std::streambuf -// { -// membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } buffer(const_cast(dict), sizeof(dict) - 1); - -// std::istream is_dict(&buffer); - -// cif::validator validator(is_dict); - -// cif::file f; - -// // -------------------------------------------------------------------- - -// const char data[] = R"( -// data_test -// loop_ -// _cat_1.id -// _cat_1.name -// _cat_1.desc -// 1 aap Aap -// 2 noot Noot -// 3 mies Mies - -// loop_ -// _cat_2.id -// _cat_2.name -// _cat_2.num -// _cat_2.desc -// 1 aap 1 'Een dier' -// 2 aap 2 'Een andere aap' -// 3 noot 1 'walnoot bijvoorbeeld' - -// loop_ -// _cat_3.id -// _cat_3.name -// _cat_3.num -// 1 aap 1 -// 2 aap 2 -// )"; - -// using namespace cif::literals; - -// struct data_membuf : public std::streambuf -// { -// data_membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } data_buffer(const_cast(data), sizeof(data) - 1); - -// std::istream is_data(&data_buffer); -// f.load(is_data, validator); - -// auto &cat1 = f.front()["cat_1"]; -// auto &cat2 = f.front()["cat_2"]; -// auto &cat3 = f.front()["cat_3"]; - -// cat3.update_value("name"_key == "aap" and "num"_key == 1, "name", "aapje"); - -// CHECK(cat3.size() == 2); - -// { -// int id, num; -// std::string name; -// cif::tie(id, name, num) = cat3.front().get("id", "name", "num"); -// CHECK(id == 1); -// CHECK(num == 1); -// CHECK(name == "aapje"); - -// cif::tie(id, name, num) = cat3.back().get("id", "name", "num"); -// CHECK(id == 2); -// CHECK(num == 2); -// CHECK(name == "aap"); -// } - -// int i = 0; -// for (const auto &[id, name, num, desc] : cat2.rows("id", "name", "num", "desc")) -// { -// switch (++i) -// { -// case 1: -// CHECK(id == 1); -// CHECK(num == 1); -// CHECK(name == "aapje"); -// CHECK(desc == "Een dier"); -// break; - -// case 2: -// CHECK(id == 2); -// CHECK(num == 2); -// CHECK(name == "aap"); -// CHECK(desc == "Een andere aap"); -// break; - -// case 3: -// CHECK(id == 3); -// CHECK(num == 1); -// CHECK(name == "noot"); -// CHECK(desc == "walnoot bijvoorbeeld"); -// break; - -// default: -// CHECK(false /*"Unexpected record"*/); -// } -// } - -// CHECK(cat1.size() == 4); -// i = 0; -// for (const auto &[id, name, desc] : cat1.rows("id", "name", "desc")) -// { -// switch (++i) -// { -// case 1: -// CHECK(id == 1); -// CHECK(name == "aapje"); -// CHECK(desc == "Aap"); -// break; - -// case 2: -// CHECK(id == 2); -// CHECK(name == "noot"); -// CHECK(desc == "Noot"); -// break; - -// case 3: -// CHECK(id == 3); -// CHECK(name == "mies"); -// CHECK(desc == "Mies"); -// break; - -// case 4: -// CHECK(id == 4); -// CHECK(name == "aap"); -// CHECK(desc == "Aap"); -// break; - -// default: -// CHECK(false /* "Unexpected record" */); -// } -// } - -// // f.save(std::cout); -// } - -// TEST_CASE("pc_1") -// { -// /* -// Parent/child tests - -// Note that the dictionary is different than the one in test r1 -// */ - -// const char dict[] = R"( -// data_test_dict.dic -// _datablock.id test_dict.dic -// _datablock.description -// ; -// A test dictionary -// ; -// _dictionary.title test_dict.dic -// _dictionary.datablock_id test_dict.dic -// _dictionary.version 1.0 - -// loop_ -// _item_type_list.code -// _item_type_list.primitive_code -// _item_type_list.construct -// code char -// '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' - -// text char -// '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' - -// int numb -// '[+-]?[0-9]+' - -// save_cat_1 -// _category.description 'A simple test category' -// _category.id cat_1 -// _category.mandatory_code no -// _category_key.name '_cat_1.id' -// save_ - -// save__cat_1.id -// _item.name '_cat_1.id' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_linked.child_name '_cat_2.parent_id' -// _item_linked.parent_name '_cat_1.id' -// _item_type.code int -// save_ - -// save__cat_1.name -// _item.name '_cat_1.name' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_type.code code -// save_ - -// save__cat_1.desc -// _item.name '_cat_1.desc' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_type.code text -// save_ - -// save_cat_2 -// _category.description 'A second simple test category' -// _category.id cat_2 -// _category.mandatory_code no -// _category_key.name '_cat_2.id' -// save_ - -// save__cat_2.id -// _item.name '_cat_2.id' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_2.name -// _item.name '_cat_2.name' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code code -// save_ - -// save__cat_2.num -// _item.name '_cat_2.num' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_2.desc -// _item.name '_cat_2.desc' -// _item.category_id cat_2 -// _item.mandatory_code yes -// _item_type.code text -// save_ - -// save_cat_3 -// _category.description 'A third simple test category' -// _category.id cat_3 -// _category.mandatory_code no -// _category_key.name '_cat_3.id' -// save_ - -// save__cat_3.id -// _item.name '_cat_3.id' -// _item.category_id cat_3 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_3.name -// _item.name '_cat_3.name' -// _item.category_id cat_3 -// _item.mandatory_code yes -// _item_type.code code -// save_ - -// save__cat_3.num -// _item.name '_cat_3.num' -// _item.category_id cat_3 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// loop_ -// _pdbx_item_linked_group_list.parent_category_id -// _pdbx_item_linked_group_list.link_group_id -// _pdbx_item_linked_group_list.parent_name -// _pdbx_item_linked_group_list.child_name -// _pdbx_item_linked_group_list.child_category_id -// cat_1 1 '_cat_1.name' '_cat_2.name' cat_2 -// cat_2 1 '_cat_2.name' '_cat_3.name' cat_3 -// cat_2 1 '_cat_2.num' '_cat_3.num' cat_3 - -// )"; - -// struct membuf : public std::streambuf -// { -// membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } buffer(const_cast(dict), sizeof(dict) - 1); - -// std::istream is_dict(&buffer); - -// cif::validator validator(is_dict); - -// cif::file f; - -// // -------------------------------------------------------------------- - -// const char data[] = R"( -// data_test -// loop_ -// _cat_1.id -// _cat_1.name -// _cat_1.desc -// 1 aap Aap -// 2 noot Noot -// 3 mies Mies - -// loop_ -// _cat_2.id -// _cat_2.name -// _cat_2.num -// _cat_2.desc -// 1 aap 1 'Een dier' -// 2 aap 2 'Een andere aap' -// 3 noot 1 'walnoot bijvoorbeeld' - -// loop_ -// _cat_3.id -// _cat_3.name -// _cat_3.num -// 1 aap 1 -// 2 aap 2 -// )"; - -// using namespace cif::literals; - -// struct data_membuf : public std::streambuf -// { -// data_membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } data_buffer(const_cast(data), sizeof(data) - 1); - -// std::istream is_data(&data_buffer); -// f.load(is_data, validator); - -// auto &cat1 = f.front()["cat_1"]; -// auto &cat2 = f.front()["cat_2"]; -// auto &cat3 = f.front()["cat_3"]; - -// // some parent/child tests - -// // find all children in cat2 for the row with id == 1 in cat1 -// auto rs1 = cat1.get_children(cat1.find1("id"_key == 1), cat2); -// CHECK(rs1.size() == 2); - -// auto rs2 = cat1.get_children(cat1.find1("id"_key == 2), cat2); -// CHECK(rs2.size() == 1); - -// auto rs3 = cat1.get_children(cat1.find1("id"_key == 3), cat2); -// CHECK(rs3.size() == 0); - -// // finding parents -// auto rs4 = cat2.get_parents(cat2.find1("id"_key == 1), cat1); -// CHECK(rs4.size() == 1); - -// auto rs5 = cat3.get_parents(cat3.find1("id"_key == 1), cat2); -// CHECK(rs5.size() == 1); - -// // This link is not defined: -// auto rs6 = cat3.get_parents(cat3.find1("id"_key == 1), cat1); -// CHECK(rs6.size() == 0); -// } - -// // -------------------------------------------------------------------- - -// // TEST_CASE("bondmap_1") -// // { -// // cif::VERBOSE = 2; - -// // // sections taken from CCD compounds.cif -// // auto components = R"( -// // data_ASN -// // loop_ -// // _chem_comp_bond.comp_id -// // _chem_comp_bond.atom_id_1 -// // _chem_comp_bond.atom_id_2 -// // _chem_comp_bond.value_order -// // _chem_comp_bond.pdbx_aromatic_flag -// // _chem_comp_bond.pdbx_stereo_config -// // _chem_comp_bond.pdbx_ordinal -// // ASN N CA SING N N 1 -// // ASN N H SING N N 2 -// // ASN N H2 SING N N 3 -// // ASN CA C SING N N 4 -// // ASN CA CB SING N N 5 -// // ASN CA HA SING N N 6 -// // ASN C O DOUB N N 7 -// // ASN C OXT SING N N 8 -// // ASN CB CG SING N N 9 -// // ASN CB HB2 SING N N 10 -// // ASN CB HB3 SING N N 11 -// // ASN CG OD1 DOUB N N 12 -// // ASN CG ND2 SING N N 13 -// // ASN ND2 HD21 SING N N 14 -// // ASN ND2 HD22 SING N N 15 -// // ASN OXT HXT SING N N 16 -// // data_PHE -// // loop_ -// // _chem_comp_bond.comp_id -// // _chem_comp_bond.atom_id_1 -// // _chem_comp_bond.atom_id_2 -// // _chem_comp_bond.value_order -// // _chem_comp_bond.pdbx_aromatic_flag -// // _chem_comp_bond.pdbx_stereo_config -// // _chem_comp_bond.pdbx_ordinal -// // PHE N CA SING N N 1 -// // PHE N H SING N N 2 -// // PHE N H2 SING N N 3 -// // PHE CA C SING N N 4 -// // PHE CA CB SING N N 5 -// // PHE CA HA SING N N 6 -// // PHE C O DOUB N N 7 -// // PHE C OXT SING N N 8 -// // PHE CB CG SING N N 9 -// // PHE CB HB2 SING N N 10 -// // PHE CB HB3 SING N N 11 -// // PHE CG CD1 DOUB Y N 12 -// // PHE CG CD2 SING Y N 13 -// // PHE CD1 CE1 SING Y N 14 -// // PHE CD1 HD1 SING N N 15 -// // PHE CD2 CE2 DOUB Y N 16 -// // PHE CD2 HD2 SING N N 17 -// // PHE CE1 CZ DOUB Y N 18 -// // PHE CE1 HE1 SING N N 19 -// // PHE CE2 CZ SING Y N 20 -// // PHE CE2 HE2 SING N N 21 -// // PHE CZ HZ SING N N 22 -// // PHE OXT HXT SING N N 23 -// // data_PRO -// // loop_ -// // _chem_comp_bond.comp_id -// // _chem_comp_bond.atom_id_1 -// // _chem_comp_bond.atom_id_2 -// // _chem_comp_bond.value_order -// // _chem_comp_bond.pdbx_aromatic_flag -// // _chem_comp_bond.pdbx_stereo_config -// // _chem_comp_bond.pdbx_ordinal -// // PRO N CA SING N N 1 -// // PRO N CD SING N N 2 -// // PRO N H SING N N 3 -// // PRO CA C SING N N 4 -// // PRO CA CB SING N N 5 -// // PRO CA HA SING N N 6 -// // PRO C O DOUB N N 7 -// // PRO C OXT SING N N 8 -// // PRO CB CG SING N N 9 -// // PRO CB HB2 SING N N 10 -// // PRO CB HB3 SING N N 11 -// // PRO CG CD SING N N 12 -// // PRO CG HG2 SING N N 13 -// // PRO CG HG3 SING N N 14 -// // PRO CD HD2 SING N N 15 -// // PRO CD HD3 SING N N 16 -// // PRO OXT HXT SING N N 17 -// // )"_cf; - -// // const std::filesystem::path example(gTestDir / ".." / "examples" / "1cbs.cif.gz"); -// // mmcif::File file(example.string()); -// // mmcif::Structure structure(file); - -// // (void)file.isValid(); - -// // mmcif::BondMap bm(structure); - -// // // Test the bonds of the first three residues, that's PRO A 1, ASN A 2, PHE A 3 - -// // for (const auto &[compound, seqnr] : std::initializer_list>{{"PRO", 1}, {"ASN", 2}, {"PHE", 3}}) -// // { -// // auto &res = structure.get_residue("A", compound, seqnr, ""); -// // auto atoms = res.atoms(); - -// // auto dc = components.get(compound); -// // CHECK(dc != nullptr); - -// // auto cc = dc->get("chem_comp_bond"); -// // CHECK(cc != nullptr); - -// // std::set> bonded; - -// // for (const auto &[atom_id_1, atom_id_2] : cc->rows("atom_id_1", "atom_id_2")) -// // { -// // if (atom_id_1 > atom_id_2) -// // bonded.insert({atom_id_2, atom_id_1}); -// // else -// // bonded.insert({atom_id_1, atom_id_2}); -// // } - -// // for (std::size_t i = 0; i + 1 < atoms.size(); ++i) -// // { -// // auto label_i = atoms[i].labelAtomID(); - -// // for (std::size_t j = i + 1; j < atoms.size(); ++j) -// // { -// // auto label_j = atoms[j].labelAtomID(); - -// // bool bonded_1 = bm(atoms[i], atoms[j]); -// // bool bonded_1_i = bm(atoms[j], atoms[i]); - -// // bool bonded_t = label_i > label_j -// // ? bonded.count({label_j, label_i}) -// // : bonded.count({label_i, label_j}); - -// // CHECK(bonded_1 == bonded_t); -// // CHECK(bonded_1_i == bonded_t); -// // } -// // } -// // } - -// // // And check the inter-aminoacid links - -// // auto &poly = structure.polymers().front(); - -// // for (std::size_t i = 0; i + 1 < poly.size(); ++i) -// // { -// // auto C = poly[i].atomByID("C"); -// // auto N = poly[i + 1].atomByID("N"); - -// // CHECK(bm(C, N)); -// // CHECK(bm(N, C)); -// // } -// // } - -// // TEST_CASE("bondmap_2") -// // { -// // CHECK_THROWS_AS(mmcif::BondMap::atomIDsForCompound("UN_"), mmcif::BondMapException); - -// // mmcif::CompoundFactory::instance().pushDictionary(gTestDir / "UN_.cif"); - -// // CHECK(mmcif::BondMap::atomIDsForCompound("UN_").empty() == false); -// // } - -// TEST_CASE("reading_file_1") -// { -// std::istringstream is("Hello, world!"); - -// cif::file file; -// CHECK_THROWS_AS(file.load(is), std::runtime_error); -// } - -// TEST_CASE("parser_test_1") -// { -// auto data1 = R"( -// data_QM -// _test.text ?? +// _chem_comp_bond.comp_id +// _chem_comp_bond.atom_id_1 +// _chem_comp_bond.atom_id_2 +// _chem_comp_bond.value_order +// _chem_comp_bond.pdbx_aromatic_flag +// _chem_comp_bond.pdbx_stereo_config +// _chem_comp_bond.pdbx_ordinal +// PRO N CA SING N N 1 +// PRO N CD SING N N 2 +// PRO N H SING N N 3 +// PRO CA C SING N N 4 +// PRO CA CB SING N N 5 +// PRO CA HA SING N N 6 +// PRO C O DOUB N N 7 +// PRO C OXT SING N N 8 +// PRO CB CG SING N N 9 +// PRO CB HB2 SING N N 10 +// PRO CB HB3 SING N N 11 +// PRO CG CD SING N N 12 +// PRO CG HG2 SING N N 13 +// PRO CG HG3 SING N N 14 +// PRO CD HD2 SING N N 15 +// PRO CD HD3 SING N N 16 +// PRO OXT HXT SING N N 17 // )"_cf; -// auto &db1 = data1.front(); -// auto &test1 = db1["test"]; +// const std::filesystem::path example(gTestDir / ".." / "examples" / "1cbs.cif.gz"); +// mmcif::File file(example.string()); +// mmcif::Structure structure(file); -// CHECK(test1.size() == 1); +// (void)file.isValid(); -// for (auto r : test1) +// mmcif::BondMap bm(structure); + +// // Test the bonds of the first three residues, that's PRO A 1, ASN A 2, PHE A 3 + +// for (const auto &[compound, seqnr] : std::initializer_list>{{"PRO", 1}, {"ASN", 2}, {"PHE", 3}}) // { -// auto text = r.get("text"); -// CHECK(text == "??"); -// } +// auto &res = structure.get_residue("A", compound, seqnr, ""); +// auto atoms = res.atoms(); -// std::stringstream ss; -// data1.save(ss); +// auto dc = components.get(compound); +// CHECK(dc != nullptr); -// auto data2 = cif::file(ss); +// auto cc = dc->get("chem_comp_bond"); +// CHECK(cc != nullptr); -// auto &db2 = data2.front(); -// auto &test2 = db2["test"]; +// std::set> bonded; -// CHECK(test2.size() == 1); - -// for (auto r : test2) -// { -// auto text = r.get("text"); -// CHECK(text == "??"); -// } -// } - -// TEST_CASE("output_test_1") -// { -// auto data1 = R"( -// data_Q -// loop_ -// _test.text -// stop_the_crap -// 'and stop_ this too' -// 'data_dinges' -// blablaglobal_bla -// boo.data_.whatever -// 'data_.whatever' -// 'stop_' -// 'loop_' -// 'global_' -// '_with.leading_underscore' -// )"_cf; - -// auto &db1 = data1.front(); -// auto &test1 = db1["test"]; - -// struct T -// { -// const char *s; -// bool q; -// } kS[] = { -// { "stop_the_crap", true }, -// { "and stop_ this too", false }, -// { "data_dinges", false }, -// { "blablaglobal_bla", true }, -// { "boo.data_.whatever", true }, -// { "data_.whatever", false }, -// { "stop_", false }, -// { "loop_", false }, -// { "global_", false }, -// { "_with.leading_underscore", false } -// }; - -// CHECK(test1.size() == sizeof(kS) / sizeof(T)); - -// std::size_t i = 0; -// for (auto r : test1) -// { -// auto text = r.get("text"); -// CHECK(text == kS[i].s); -// CHECK(cif::sac_parser::is_unquoted_string(kS[i].s) == kS[i].q); -// ++i; -// } - -// std::stringstream ss; -// data1.save(ss); - -// auto data2 = cif::file(ss); - -// auto &db2 = data2.front(); -// auto &test2 = db2["test"]; - -// CHECK(test2.size() == sizeof(kS) / sizeof(T)); - -// i = 0; -// for (auto r : test2) -// { -// auto text = r.get("text"); -// CHECK(text == kS[i++].s); -// } -// } - -// TEST_CASE("output_test_2") -// { -// auto data1 = R"( -// data_Q -// loop_ -// _test.text -// ;A very, very loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong line -// ; -// ;A line with a newline, look: -// There it was! -// ; -// )"_cf; - -// auto &db1 = data1.front(); -// auto &test1 = db1["test"]; - -// struct T -// { -// const char *s; -// bool q; -// } kS[] = { -// { "A very, very loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong line", false }, -// { R"(A line with a newline, look: -// There it was!)", -// false } -// }; - -// CHECK(test1.size() == sizeof(kS) / sizeof(T)); - -// std::size_t i = 0; -// for (auto r : test1) -// { -// auto text = r.get("text"); -// CHECK(text == kS[i].s); -// CHECK(cif::sac_parser::is_unquoted_string(kS[i].s) == kS[i].q); -// ++i; -// } - -// std::stringstream ss; -// data1.save(ss); - -// auto data2 = cif::file(ss); - -// auto &db2 = data2.front(); -// auto &test2 = db2["test"]; - -// CHECK(test2.size() == sizeof(kS) / sizeof(T)); - -// i = 0; -// for (auto r : test2) -// { -// auto text = r.get("text"); -// CHECK(text == kS[i++].s); -// } -// } - -// TEST_CASE("trim_test") -// { -// CHECK(cif::trim_copy("aap") == "aap"); -// CHECK(cif::trim_copy(" aap") == "aap"); -// CHECK(cif::trim_copy(" aap ") == "aap"); -// CHECK(cif::trim_copy("aap ") == "aap"); -// CHECK(cif::trim_copy(" aap ") == "aap"); - -// CHECK(cif::trim_left_copy("aap") == "aap"); -// CHECK(cif::trim_left_copy(" aap") == "aap"); -// CHECK(cif::trim_left_copy(" aap ") == "aap "); -// CHECK(cif::trim_left_copy("aap ") == "aap "); -// CHECK(cif::trim_left_copy("aap ") == "aap "); - -// CHECK(cif::trim_right_copy("aap") == "aap"); -// CHECK(cif::trim_right_copy(" aap") == " aap"); -// CHECK(cif::trim_right_copy(" aap ") == " aap"); -// CHECK(cif::trim_right_copy("aap ") == "aap"); -// CHECK(cif::trim_right_copy(" aap ") == " aap"); - -// std::string s; - -// s = "aap"; -// cif::trim(s); -// CHECK(s == "aap"); -// s = " aap"; -// cif::trim(s); -// CHECK(s == "aap"); -// s = " aap "; -// cif::trim(s); -// CHECK(s == "aap"); -// s = "aap "; -// cif::trim(s); -// CHECK(s == "aap"); -// s = " aap "; -// cif::trim(s); -// CHECK(s == "aap"); - -// s = "aap"; -// cif::trim_left(s); -// CHECK(s == "aap"); -// s = " aap"; -// cif::trim_left(s); -// CHECK(s == "aap"); -// s = " aap "; -// cif::trim_left(s); -// CHECK(s == "aap "); -// s = "aap "; -// cif::trim_left(s); -// CHECK(s == "aap "); -// s = "aap "; -// cif::trim_left(s); -// CHECK(s == "aap "); - -// s = "aap"; -// cif::trim_right(s); -// CHECK(s == "aap"); -// s = " aap"; -// cif::trim_right(s); -// CHECK(s == " aap"); -// s = " aap "; -// cif::trim_right(s); -// CHECK(s == " aap"); -// s = "aap "; -// cif::trim_right(s); -// CHECK(s == "aap"); -// s = " aap "; -// cif::trim_right(s); -// CHECK(s == " aap"); -// } - -// TEST_CASE("split_test") -// { -// std::vector v, t; - -// v = cif::split<>("aap;noot;mies", ";"); -// t = std::vector{ "aap", "noot", "mies" }; - -// CHECK(v == t); - -// v = cif::split("aap;noot,mies", ";,"); -// // t = std::vector{ "aap", "noot", "mies" }; - -// CHECK(v == t); - -// v = cif::split(";aap;noot,mies;", ";,"); -// t = std::vector{ "", "aap", "noot", "mies", "" }; - -// CHECK(v == t); - -// v = cif::split(";aap;noot,mies;", ";,", true); -// t = std::vector{ "aap", "noot", "mies" }; - -// CHECK(v == t); -// } - -// TEST_CASE("join_test") -// { -// CHECK(cif::join(std::vector{ "aap" }, ", ") == "aap"); -// CHECK(cif::join(std::vector{ "aap", "noot" }, ", ") == "aap, noot"); -// CHECK(cif::join(std::vector{ "aap", "noot", "mies" }, ", ") == "aap, noot, mies"); -// } - -// TEST_CASE("replace_all_test") -// { -// std::string s("aap, noot, mies"); -// cif::replace_all(s, ", ", ","); -// CHECK(s == "aap,noot,mies"); - -// cif::replace_all(s, ",", ", "); -// CHECK(s == "aap, noot, mies"); - -// cif::replace_all(s, ", ", ", "); -// CHECK(s == "aap, noot, mies"); -// } - -// // -------------------------------------------------------------------- - -// TEST_CASE("reorder_test") -// { - -// const char dict[] = R"( -// data_test_dict.dic -// _datablock.id test_dict.dic -// _datablock.description -// ; -// A test dictionary -// ; -// _dictionary.title test_dict.dic -// _dictionary.datablock_id test_dict.dic -// _dictionary.version 1.0 - -// loop_ -// _item_type_list.code -// _item_type_list.primitive_code -// _item_type_list.construct -// _item_type_list.detail -// code char -// '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' -// ; code item types/single words ... -// ; -// text char -// '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' -// ; text item types / multi-line text ... -// ; -// int numb -// '[+-]?[0-9]+' -// ; int item types are the subset of numbers that are the negative -// or positive integers. -// ; - -// save_cat_1 -// _category.description 'A simple test category' -// _category.id cat_1 -// _category.mandatory_code no -// _category_key.name '_cat_1.id' - -// save_ - -// save__cat_1.id -// _item.name '_cat_1.id' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_aliases.dictionary cif_core.dic -// _item_aliases.version 2.0.1 -// _item_type.code code -// save_ - -// save__cat_1.name -// _item.name '_cat_1.name' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_aliases.dictionary cif_core.dic -// _item_aliases.version 2.0.1 -// _item_type.code text -// save_ -// )"; - -// struct membuf : public std::streambuf -// { -// membuf(char *text, std::size_t length) +// for (const auto &[atom_id_1, atom_id_2] : cc->rows("atom_id_1", "atom_id_2")) // { -// this->setg(text, text, text + length); +// if (atom_id_1 > atom_id_2) +// bonded.insert({atom_id_2, atom_id_1}); +// else +// bonded.insert({atom_id_1, atom_id_2}); // } -// } buffer(const_cast(dict), sizeof(dict) - 1); -// std::istream is_dict(&buffer); - -// cif::validator validator(is_dict); - -// cif::file f; - -// // -------------------------------------------------------------------- - -// const char data[] = R"( -// data_test -// loop_ -// _cat_1.id -// _cat_1.name -// 2 Noot -// 1 Aap -// 3 Mies -// )"; - -// struct data_membuf : public std::streambuf -// { -// data_membuf(char *text, std::size_t length) +// for (std::size_t i = 0; i + 1 < atoms.size(); ++i) // { -// this->setg(text, text, text + length); +// auto label_i = atoms[i].labelAtomID(); + +// for (std::size_t j = i + 1; j < atoms.size(); ++j) +// { +// auto label_j = atoms[j].labelAtomID(); + +// bool bonded_1 = bm(atoms[i], atoms[j]); +// bool bonded_1_i = bm(atoms[j], atoms[i]); + +// bool bonded_t = label_i > label_j +// ? bonded.count({label_j, label_i}) +// : bonded.count({label_i, label_j}); + +// CHECK(bonded_1 == bonded_t); +// CHECK(bonded_1_i == bonded_t); +// } // } -// } data_buffer(const_cast(data), sizeof(data) - 1); +// } -// std::istream is_data(&data_buffer); -// f.load(is_data, validator); +// // And check the inter-aminoacid links -// CHECK(f.is_valid()); +// auto &poly = structure.polymers().front(); -// auto &cat1 = f.front()["cat_1"]; -// cat1.reorder_by_index(); - -// int n = 1; - -// const char *ts[] = { "Aap", "Noot", "Mies" }; - -// for (const auto &[id, name] : cat1.rows("id", "name")) +// for (std::size_t i = 0; i + 1 < poly.size(); ++i) // { -// CHECK(id == n); -// CHECK(name == ts[n - 1]); -// ++n; +// auto C = poly[i].atomByID("C"); +// auto N = poly[i + 1].atomByID("N"); + +// CHECK(bm(C, N)); +// CHECK(bm(N, C)); // } // } -// // -------------------------------------------------------------------- - -// TEST_CASE("audit_conform_test") +// TEST_CASE("bondmap_2") // { +// CHECK_THROWS_AS(mmcif::BondMap::atomIDsForCompound("UN_"), mmcif::BondMapException); -// const char dict[] = R"( -// data_test_dict.dic -// _datablock.id test_dict.dic -// _datablock.description -// ; -// A test dictionary -// ; -// _dictionary.title test_dict.dic -// _dictionary.datablock_id test_dict.dic -// _dictionary.version 1.0 +// mmcif::CompoundFactory::instance().pushDictionary(gTestDir / "UN_.cif"); -// loop_ -// _item_type_list.code -// _item_type_list.primitive_code -// _item_type_list.construct -// _item_type_list.detail -// code char -// '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' -// ; code item types/single words ... -// ; -// text char -// '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' -// ; text item types / multi-line text ... -// ; -// int numb -// '[+-]?[0-9]+' -// ; int item types are the subset of numbers that are the negative -// or positive integers. -// ; +// CHECK(mmcif::BondMap::atomIDsForCompound("UN_").empty() == false); +// } -// ################### -// ## AUDIT_CONFORM ## -// ################### +TEST_CASE("reading_file_1") +{ + std::istringstream is("Hello, world!"); -// save_audit_conform -// _category.description -// ; Data items in the AUDIT_CONFORM category describe the -// dictionary versions against which the data names appearing in -// the current data block are conformant. -// ; -// _category.id audit_conform -// _category.mandatory_code no -// loop_ -// _category_key.name '_audit_conform.dict_name' -// '_audit_conform.dict_version' -// loop_ -// _category_group.id 'inclusive_group' -// 'audit_group' -// loop_ -// _category_examples.detail -// _category_examples.case -// # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// ; -// Example 1 - any file conforming to the current CIF core dictionary. -// ; -// ; -// _audit_conform.dict_name cif_core.dic -// _audit_conform.dict_version 2.3.1 -// _audit_conform.dict_location -// ftp://ftp.iucr.org/pub/cif_core.2.3.1.dic -// ; -// # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// save_ + cif::file file; + CHECK_THROWS_AS(file.load(is), std::runtime_error); +} -// save__audit_conform.dict_location -// _item_description.description -// ; A file name or uniform resource locator (URL) for the -// dictionary to which the current data block conforms. -// ; -// _item.name '_audit_conform.dict_location' -// _item.category_id audit_conform -// _item.mandatory_code no -// _item_aliases.alias_name '_audit_conform_dict_location' -// _item_aliases.dictionary cif_core.dic -// _item_aliases.version 2.0.1 -// _item_type.code text -// save_ +TEST_CASE("parser_test_1") +{ + auto data1 = R"( +data_QM +_test.text ?? +)"_cf; -// save__audit_conform.dict_name -// _item_description.description -// ; The string identifying the highest-level dictionary defining -// data names used in this file. -// ; -// _item.name '_audit_conform.dict_name' -// _item.category_id audit_conform -// _item.mandatory_code yes -// _item_aliases.alias_name '_audit_conform_dict_name' -// _item_aliases.dictionary cif_core.dic -// _item_aliases.version 2.0.1 -// _item_type.code text -// save_ + auto &db1 = data1.front(); + auto &test1 = db1["test"]; -// save__audit_conform.dict_version -// _item_description.description -// ; The version number of the dictionary to which the current -// data block conforms. -// ; -// _item.name '_audit_conform.dict_version' -// _item.category_id audit_conform -// _item.mandatory_code yes -// _item_aliases.alias_name '_audit_conform_dict_version' -// _item_aliases.dictionary cif_core.dic -// _item_aliases.version 2.0.1 -// _item_type.code text -// save_ + CHECK(test1.size() == 1); -// save_cat_1 -// _category.description 'A simple test category' -// _category.id cat_1 -// _category.mandatory_code no -// _category_key.name '_cat_1.id' + for (auto r : test1) + { + auto text = r.get("text"); + CHECK(text == "??"); + } -// save_ + std::stringstream ss; + data1.save(ss); -// save__cat_1.id -// _item.name '_cat_1.id' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_aliases.dictionary cif_core.dic -// _item_aliases.version 2.0.1 -// _item_type.code code -// save_ + auto data2 = cif::file(ss); -// save__cat_1.name -// _item.name '_cat_1.name' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_aliases.dictionary cif_core.dic -// _item_aliases.version 2.0.1 -// _item_type.code text -// save_ -// )"; + auto &db2 = data2.front(); + auto &test2 = db2["test"]; -// struct membuf : public std::streambuf -// { -// membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } buffer(const_cast(dict), sizeof(dict) - 1); + CHECK(test2.size() == 1); -// std::istream is_dict(&buffer); + for (auto r : test2) + { + auto text = r.get("text"); + CHECK(text == "??"); + } +} -// auto &validator = cif::validator_factory::instance().add(cif::validator(is_dict)); +TEST_CASE("output_test_1") +{ + auto data1 = R"( +data_Q +loop_ +_test.text +stop_the_crap +'and stop_ this too' +'data_dinges' +blablaglobal_bla +boo.data_.whatever +'data_.whatever' +'stop_' +'loop_' +'global_' +'_with.leading_underscore' +)"_cf; -// cif::file f; + auto &db1 = data1.front(); + auto &test1 = db1["test"]; -// // -------------------------------------------------------------------- + struct T + { + const char *s; + bool q; + } kS[] = { + { "stop_the_crap", true }, + { "and stop_ this too", false }, + { "data_dinges", false }, + { "blablaglobal_bla", true }, + { "boo.data_.whatever", true }, + { "data_.whatever", false }, + { "stop_", false }, + { "loop_", false }, + { "global_", false }, + { "_with.leading_underscore", false } + }; -// const char data[] = R"( -// data_test -// # -// _audit_conform.dict_name test_dict.dic -// _audit_conform.dict_version 1.0 -// # -// loop_ -// _cat_1.id -// _cat_1.name -// 2 Noot -// 1 Aap -// 3 Mies -// )"; - -// struct data_membuf : public std::streambuf -// { -// data_membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } data_buffer(const_cast(data), sizeof(data) - 1); - -// std::istream is_data(&data_buffer); -// f.load(is_data, validator); - -// CHECK(f.is_valid()); - -// std::stringstream ss; -// ss << f; - -// cif::file f2(ss); -// CHECK(f2.empty() == false); -// f2.front().load_dictionary(); -// CHECK(f2.is_valid()); - -// auto &audit_conform = f2.front()["audit_conform"]; -// CHECK(audit_conform.front()["dict_name"].as() == "test_dict.dic"); -// CHECK(audit_conform.front()["dict_version"].as() == 1.0); -// } - -// // -------------------------------------------------------------------- - -// TEST_CASE("ix_op_1") -// { -// const char dict[] = R"( -// data_test_dict.dic -// _datablock.id test_dict.dic -// _datablock.description -// ; -// A test dictionary -// ; -// _dictionary.title test_dict.dic -// _dictionary.datablock_id test_dict.dic -// _dictionary.version 1.0 - -// loop_ -// _item_type_list.code -// _item_type_list.primitive_code -// _item_type_list.construct -// code char -// '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' - -// text char -// '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' - -// int numb -// '[+-]?[0-9]+' - -// save_cat_1 -// _category.description 'A simple test category' -// _category.id cat_1 -// _category.mandatory_code yes -// loop_ -// _category_key.name '_cat_1.id' -// '_cat_1.id_2' -// save_ - -// save__cat_1.id -// _item.name '_cat_1.id' -// _item.category_id cat_1 -// _item.mandatory_code yes -// _item_type.code int -// save_ - -// save__cat_1.id_2 -// _item.name '_cat_1.id_2' -// _item.category_id cat_1 -// _item.mandatory_code no -// _item_type.code int -// save_ -// )"; - -// struct membuf : public std::streambuf -// { -// membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } buffer(const_cast(dict), sizeof(dict) - 1); - -// std::istream is_dict(&buffer); - -// cif::validator validator(is_dict); - -// cif::file f; - -// // -------------------------------------------------------------------- - -// const char data[] = R"( -// data_test -// loop_ -// _cat_1.id -// _cat_1.id_2 -// 1 10 -// 2 20 -// 3 ? -// )"; - -// // -------------------------------------------------------------------- - -// struct data_membuf : public std::streambuf -// { -// data_membuf(char *text, std::size_t length) -// { -// this->setg(text, text, text + length); -// } -// } data_buffer(const_cast(data), sizeof(data) - 1); - -// std::istream is_data(&data_buffer); -// f.load(is_data, validator); - -// auto &cat1 = f.front()["cat_1"]; - -// using key_type = cif::category::key_type; -// using test_tuple_type = std::tuple; - -// test_tuple_type TESTS[] = { -// { {{"id", "1"}, {"id_2", "10"}}, true }, -// { {{"id_2", "10"}, {"id", "1"}}, true }, -// { {{"id", "1"}, {"id_2", "20"}}, false }, -// { {{"id", "3"} }, true }, -// }; - -// for (const auto &[key, test] : TESTS) -// CHECK((bool)cat1[key] == test); -// } - -// // -------------------------------------------------------------------- - -// TEST_CASE("cifv1_0_1") -// { -// auto f = R"(data_TEST + CHECK(test1.size() == sizeof(kS) / sizeof(T)); + + std::size_t i = 0; + for (auto r : test1) + { + auto text = r.get("text"); + CHECK(text == kS[i].s); + CHECK(cif::sac_parser::is_unquoted_string(kS[i].s) == kS[i].q); + ++i; + } + + std::stringstream ss; + data1.save(ss); + + auto data2 = cif::file(ss); + + auto &db2 = data2.front(); + auto &test2 = db2["test"]; + + CHECK(test2.size() == sizeof(kS) / sizeof(T)); + + i = 0; + for (auto r : test2) + { + auto text = r.get("text"); + CHECK(text == kS[i++].s); + } +} + +TEST_CASE("output_test_2") +{ + auto data1 = R"( +data_Q +loop_ +_test.text +;A very, very loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong line +; +;A line with a newline, look: +There it was! +; +)"_cf; + + auto &db1 = data1.front(); + auto &test1 = db1["test"]; + + struct T + { + const char *s; + bool q; + } kS[] = { + { "A very, very loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong line", false }, + { R"(A line with a newline, look: +There it was!)", + false } + }; + + CHECK(test1.size() == sizeof(kS) / sizeof(T)); + + std::size_t i = 0; + for (auto r : test1) + { + auto text = r.get("text"); + CHECK(text == kS[i].s); + CHECK(cif::sac_parser::is_unquoted_string(kS[i].s) == kS[i].q); + ++i; + } + + std::stringstream ss; + data1.save(ss); + + auto data2 = cif::file(ss); + + auto &db2 = data2.front(); + auto &test2 = db2["test"]; + + CHECK(test2.size() == sizeof(kS) / sizeof(T)); + + i = 0; + for (auto r : test2) + { + auto text = r.get("text"); + CHECK(text == kS[i++].s); + } +} + +TEST_CASE("trim_test") +{ + CHECK(cif::trim_copy("aap") == "aap"); + CHECK(cif::trim_copy(" aap") == "aap"); + CHECK(cif::trim_copy(" aap ") == "aap"); + CHECK(cif::trim_copy("aap ") == "aap"); + CHECK(cif::trim_copy(" aap ") == "aap"); + + CHECK(cif::trim_left_copy("aap") == "aap"); + CHECK(cif::trim_left_copy(" aap") == "aap"); + CHECK(cif::trim_left_copy(" aap ") == "aap "); + CHECK(cif::trim_left_copy("aap ") == "aap "); + CHECK(cif::trim_left_copy("aap ") == "aap "); + + CHECK(cif::trim_right_copy("aap") == "aap"); + CHECK(cif::trim_right_copy(" aap") == " aap"); + CHECK(cif::trim_right_copy(" aap ") == " aap"); + CHECK(cif::trim_right_copy("aap ") == "aap"); + CHECK(cif::trim_right_copy(" aap ") == " aap"); + + std::string s; + + s = "aap"; + cif::trim(s); + CHECK(s == "aap"); + s = " aap"; + cif::trim(s); + CHECK(s == "aap"); + s = " aap "; + cif::trim(s); + CHECK(s == "aap"); + s = "aap "; + cif::trim(s); + CHECK(s == "aap"); + s = " aap "; + cif::trim(s); + CHECK(s == "aap"); + + s = "aap"; + cif::trim_left(s); + CHECK(s == "aap"); + s = " aap"; + cif::trim_left(s); + CHECK(s == "aap"); + s = " aap "; + cif::trim_left(s); + CHECK(s == "aap "); + s = "aap "; + cif::trim_left(s); + CHECK(s == "aap "); + s = "aap "; + cif::trim_left(s); + CHECK(s == "aap "); + + s = "aap"; + cif::trim_right(s); + CHECK(s == "aap"); + s = " aap"; + cif::trim_right(s); + CHECK(s == " aap"); + s = " aap "; + cif::trim_right(s); + CHECK(s == " aap"); + s = "aap "; + cif::trim_right(s); + CHECK(s == "aap"); + s = " aap "; + cif::trim_right(s); + CHECK(s == " aap"); +} + +TEST_CASE("split_test") +{ + std::vector v, t; + + v = cif::split<>("aap;noot;mies", ";"); + t = std::vector{ "aap", "noot", "mies" }; + + CHECK(v == t); + + v = cif::split("aap;noot,mies", ";,"); + // t = std::vector{ "aap", "noot", "mies" }; + + CHECK(v == t); + + v = cif::split(";aap;noot,mies;", ";,"); + t = std::vector{ "", "aap", "noot", "mies", "" }; + + CHECK(v == t); + + v = cif::split(";aap;noot,mies;", ";,", true); + t = std::vector{ "aap", "noot", "mies" }; + + CHECK(v == t); +} + +TEST_CASE("join_test") +{ + CHECK(cif::join(std::vector{ "aap" }, ", ") == "aap"); + CHECK(cif::join(std::vector{ "aap", "noot" }, ", ") == "aap, noot"); + CHECK(cif::join(std::vector{ "aap", "noot", "mies" }, ", ") == "aap, noot, mies"); +} + +TEST_CASE("replace_all_test") +{ + std::string s("aap, noot, mies"); + cif::replace_all(s, ", ", ","); + CHECK(s == "aap,noot,mies"); + + cif::replace_all(s, ",", ", "); + CHECK(s == "aap, noot, mies"); + + cif::replace_all(s, ", ", ", "); + CHECK(s == "aap, noot, mies"); +} + +// -------------------------------------------------------------------- + +TEST_CASE("reorder_test") +{ + + const char dict[] = R"( +data_test_dict.dic + _datablock.id test_dict.dic + _datablock.description +; + A test dictionary +; + _dictionary.title test_dict.dic + _dictionary.datablock_id test_dict.dic + _dictionary.version 1.0 + + loop_ + _item_type_list.code + _item_type_list.primitive_code + _item_type_list.construct + _item_type_list.detail + code char + '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' +; code item types/single words ... +; + text char + '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' +; text item types / multi-line text ... +; + int numb + '[+-]?[0-9]+' +; int item types are the subset of numbers that are the negative + or positive integers. +; + +save_cat_1 + _category.description 'A simple test category' + _category.id cat_1 + _category.mandatory_code no + _category_key.name '_cat_1.id' + + save_ + +save__cat_1.id + _item.name '_cat_1.id' + _item.category_id cat_1 + _item.mandatory_code yes + _item_aliases.dictionary cif_core.dic + _item_aliases.version 2.0.1 + _item_type.code code + save_ + +save__cat_1.name + _item.name '_cat_1.name' + _item.category_id cat_1 + _item.mandatory_code yes + _item_aliases.dictionary cif_core.dic + _item_aliases.version 2.0.1 + _item_type.code text + save_ + )"; + + struct membuf : public std::streambuf + { + membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } buffer(const_cast(dict), sizeof(dict) - 1); + + std::istream is_dict(&buffer); + + cif::validator validator(is_dict); + + cif::file f; + + // -------------------------------------------------------------------- + + const char data[] = R"( +data_test +loop_ +_cat_1.id +_cat_1.name +2 Noot +1 Aap +3 Mies + )"; + + struct data_membuf : public std::streambuf + { + data_membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } data_buffer(const_cast(data), sizeof(data) - 1); + + std::istream is_data(&data_buffer); + f.load(is_data, validator); + + CHECK(f.is_valid()); + + auto &cat1 = f.front()["cat_1"]; + cat1.reorder_by_index(); + + int n = 1; + + const char *ts[] = { "Aap", "Noot", "Mies" }; + + for (const auto &[id, name] : cat1.rows("id", "name")) + { + CHECK(id == n); + CHECK(name == ts[n - 1]); + ++n; + } +} + +// -------------------------------------------------------------------- + +TEST_CASE("audit_conform_test") +{ + + const char dict[] = R"( +data_test_dict.dic + _datablock.id test_dict.dic + _datablock.description +; + A test dictionary +; + _dictionary.title test_dict.dic + _dictionary.datablock_id test_dict.dic + _dictionary.version 1.0 + + loop_ + _item_type_list.code + _item_type_list.primitive_code + _item_type_list.construct + _item_type_list.detail + code char + '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' +; code item types/single words ... +; + text char + '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' +; text item types / multi-line text ... +; + int numb + '[+-]?[0-9]+' +; int item types are the subset of numbers that are the negative + or positive integers. +; + +################### +## AUDIT_CONFORM ## +################### + +save_audit_conform + _category.description +; Data items in the AUDIT_CONFORM category describe the + dictionary versions against which the data names appearing in + the current data block are conformant. +; + _category.id audit_conform + _category.mandatory_code no + loop_ + _category_key.name '_audit_conform.dict_name' + '_audit_conform.dict_version' + loop_ + _category_group.id 'inclusive_group' + 'audit_group' + loop_ + _category_examples.detail + _category_examples.case +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +; + Example 1 - any file conforming to the current CIF core dictionary. +; +; + _audit_conform.dict_name cif_core.dic + _audit_conform.dict_version 2.3.1 + _audit_conform.dict_location + ftp://ftp.iucr.org/pub/cif_core.2.3.1.dic +; +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + save_ + +save__audit_conform.dict_location + _item_description.description +; A file name or uniform resource locator (URL) for the + dictionary to which the current data block conforms. +; + _item.name '_audit_conform.dict_location' + _item.category_id audit_conform + _item.mandatory_code no + _item_aliases.alias_name '_audit_conform_dict_location' + _item_aliases.dictionary cif_core.dic + _item_aliases.version 2.0.1 + _item_type.code text + save_ + +save__audit_conform.dict_name + _item_description.description +; The string identifying the highest-level dictionary defining + data names used in this file. +; + _item.name '_audit_conform.dict_name' + _item.category_id audit_conform + _item.mandatory_code yes + _item_aliases.alias_name '_audit_conform_dict_name' + _item_aliases.dictionary cif_core.dic + _item_aliases.version 2.0.1 + _item_type.code text + save_ + +save__audit_conform.dict_version + _item_description.description +; The version number of the dictionary to which the current + data block conforms. +; + _item.name '_audit_conform.dict_version' + _item.category_id audit_conform + _item.mandatory_code yes + _item_aliases.alias_name '_audit_conform_dict_version' + _item_aliases.dictionary cif_core.dic + _item_aliases.version 2.0.1 + _item_type.code text + save_ + +save_cat_1 + _category.description 'A simple test category' + _category.id cat_1 + _category.mandatory_code no + _category_key.name '_cat_1.id' + + save_ + +save__cat_1.id + _item.name '_cat_1.id' + _item.category_id cat_1 + _item.mandatory_code yes + _item_aliases.dictionary cif_core.dic + _item_aliases.version 2.0.1 + _item_type.code code + save_ + +save__cat_1.name + _item.name '_cat_1.name' + _item.category_id cat_1 + _item.mandatory_code yes + _item_aliases.dictionary cif_core.dic + _item_aliases.version 2.0.1 + _item_type.code text + save_ + )"; + + struct membuf : public std::streambuf + { + membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } buffer(const_cast(dict), sizeof(dict) - 1); + + std::istream is_dict(&buffer); + + auto &validator = cif::validator_factory::instance().add(cif::validator(is_dict)); + + cif::file f; + + // -------------------------------------------------------------------- + + const char data[] = R"( +data_test +# +_audit_conform.dict_name test_dict.dic +_audit_conform.dict_version 1.0 +# +loop_ +_cat_1.id +_cat_1.name +2 Noot +1 Aap +3 Mies + )"; + + struct data_membuf : public std::streambuf + { + data_membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } data_buffer(const_cast(data), sizeof(data) - 1); + + std::istream is_data(&data_buffer); + f.load(is_data, validator); + + CHECK(f.is_valid()); + + std::stringstream ss; + ss << f; + + cif::file f2(ss); + CHECK(f2.empty() == false); + f2.front().load_dictionary(); + CHECK(f2.is_valid()); + + auto &audit_conform = f2.front()["audit_conform"]; + CHECK(audit_conform.front()["dict_name"].get() == "test_dict.dic"); + CHECK(audit_conform.front()["dict_version"].get() == 1.0); +} + +// -------------------------------------------------------------------- + +TEST_CASE("ix_op_1") +{ + const char dict[] = R"( +data_test_dict.dic + _datablock.id test_dict.dic + _datablock.description +; + A test dictionary +; + _dictionary.title test_dict.dic + _dictionary.datablock_id test_dict.dic + _dictionary.version 1.0 + + loop_ + _item_type_list.code + _item_type_list.primitive_code + _item_type_list.construct + code char + '[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*' + + text char + '[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*' + + int numb + '[+-]?[0-9]+' + +save_cat_1 + _category.description 'A simple test category' + _category.id cat_1 + _category.mandatory_code yes + loop_ + _category_key.name '_cat_1.id' + '_cat_1.id_2' + save_ + +save__cat_1.id + _item.name '_cat_1.id' + _item.category_id cat_1 + _item.mandatory_code yes + _item_type.code int + save_ + +save__cat_1.id_2 + _item.name '_cat_1.id_2' + _item.category_id cat_1 + _item.mandatory_code no + _item_type.code int + save_ +)"; + + struct membuf : public std::streambuf + { + membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } buffer(const_cast(dict), sizeof(dict) - 1); + + std::istream is_dict(&buffer); + + cif::validator validator(is_dict); + + cif::file f; + + // -------------------------------------------------------------------- + + const char data[] = R"( +data_test +loop_ +_cat_1.id +_cat_1.id_2 +1 10 +2 20 +3 ? + )"; + + // -------------------------------------------------------------------- + + struct data_membuf : public std::streambuf + { + data_membuf(char *text, std::size_t length) + { + this->setg(text, text, text + length); + } + } data_buffer(const_cast(data), sizeof(data) - 1); + + std::istream is_data(&data_buffer); + f.load(is_data, validator); + + auto &cat1 = f.front()["cat_1"]; + + using key_type = cif::category::key_type; + using test_tuple_type = std::tuple; + + test_tuple_type TESTS[] = { + { {{"id", "1"}, {"id_2", "10"}}, true }, + { {{"id_2", "10"}, {"id", "1"}}, true }, + { {{"id", "1"}, {"id_2", "20"}}, false }, + { {{"id", "3"} }, true }, + }; + + for (const auto &[key, test] : TESTS) + CHECK((bool)cat1[key] == test); +} + +// -------------------------------------------------------------------- + +TEST_CASE("cifv1_0_1") +{ + auto f = R"(data_TEST +# +loop_ +_id +_name +1 aap +2 noot +3 mies +4 ? +5 . + )"_cf; + + auto &db = f.front(); + + auto &cat = db[""]; + + for (auto r : cat) + { + int id; + std::optional name; + + cif::tie(id, name) = r.get("id", "name"); + + switch (id) + { + case 1: CHECK(*name == "aap"); break; + case 2: CHECK(*name == "noot"); break; + case 3: CHECK(*name == "mies"); break; + default: CHECK(name.has_value() == false); + } + } + + std::stringstream ss; + ss << db; + + auto f2 = cif::file(ss); + auto &db2 = f2.front(); + + CHECK(db == db2); +} + +// TEST_CASE("cifv1_0_2") +// { +// CHECK_THROWS_AS(R"(data_TEST // # +// _version 1.0 // loop_ // _id // _name @@ -3429,105 +3491,61 @@ TEST_CASE("cc_3") // 3 mies // 4 ? // 5 . -// )"_cf; - -// auto &db = f.front(); - -// auto &cat = db[""]; - -// for (auto r : cat) -// { -// int id; -// std::optional name; - -// cif::tie(id, name) = r.get("id", "name"); - -// switch (id) -// { -// case 1: CHECK(*name == "aap"); break; -// case 2: CHECK(*name == "noot"); break; -// case 3: CHECK(*name == "mies"); break; -// default: CHECK(name.has_value() == false); -// } -// } - -// std::stringstream ss; -// ss << db; - -// auto f2 = cif::file(ss); -// auto &db2 = f2.front(); - -// CHECK(db == db2); +// )"_cf, cif::parse_error); // } -// // TEST_CASE("cifv1_0_2") -// // { -// // CHECK_THROWS_AS(R"(data_TEST -// // # -// // _version 1.0 -// // loop_ -// // _id -// // _name -// // 1 aap -// // 2 noot -// // 3 mies -// // 4 ? -// // 5 . -// // )"_cf, cif::parse_error); -// // } +TEST_CASE("cifv1_0_3") +{ + auto f = R"(data_TEST +# +_version 1.0 +_date today +)"_cf; -// TEST_CASE("cifv1_0_3") -// { -// auto f = R"(data_TEST -// # -// _version 1.0 -// _date today -// )"_cf; + auto &db = f.front(); -// auto &db = f.front(); + auto &cat = db[""]; + CHECK(not cat.empty()); -// auto &cat = db[""]; -// CHECK(not cat.empty()); + auto r = cat.front(); + CHECK(r["version"].get() == "1.0"); + CHECK(r["date"].get() == "today"); -// auto r = cat.front(); -// CHECK(r["version"].as() == "1.0"); -// CHECK(r["date"].as() == "today"); + std::stringstream ss; + ss << db; -// std::stringstream ss; -// ss << db; + auto f2 = cif::file(ss); + auto &db2 = f2.front(); -// auto f2 = cif::file(ss); -// auto &db2 = f2.front(); + CHECK(db == db2); +} -// CHECK(db == db2); -// } +TEST_CASE("find1_opt_1") +{ + using namespace cif::literals; + using namespace std::literals; -// TEST_CASE("find1_opt_1") -// { -// using namespace cif::literals; -// using namespace std::literals; + auto f = R"(data_TEST +# +loop_ +_test.id +_test.name +_test.value +1 aap 1.0 +2 noot 1.1 +3 mies 1.2 + )"_cf; -// auto f = R"(data_TEST -// # -// loop_ -// _test.id -// _test.name -// _test.value -// 1 aap 1.0 -// 2 noot 1.1 -// 3 mies 1.2 -// )"_cf; + auto &db = f.front(); + auto &test = db["test"]; -// auto &db = f.front(); -// auto &test = db["test"]; + auto v = test.find1>("id"_key == 1, "value"); + CHECK(v.has_value()); + CHECK(*v == 1.0f); -// auto v = test.find1>("id"_key == 1, "value"); -// CHECK(v.has_value()); -// CHECK(*v == 1.0f); - -// v = test.find1>("id"_key == 4, "value"); -// CHECK(v.has_value() == false); -// } + v = test.find1>("id"_key == 4, "value"); + CHECK(v.has_value() == false); +} // // --------------------------------------------------------------------