/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "cif++/category.hpp" #include "cif++/model.hpp" #include "cif++/row.hpp" #include "test-main.hpp" #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); std::istream is(&buffer); return cif::file(is); } // -------------------------------------------------------------------- TEST_CASE("text_1") { CHECK(cif::iequals("TEST", "test")); CHECK(cif::iequals(std::string_view{ "TEST" }, std::string_view{ "test" })); CHECK(cif::icompare("TEST", "test") == 0); CHECK(cif::icompare(std::string_view{ "TEST" }, std::string_view{ "test" }) == 0); CHECK(cif::icompare("TEST1", "test") > 0); CHECK(cif::icompare(std::string_view{ "TEST1" }, std::string_view{ "test" }) > 0); CHECK(cif::icompare("aap", "noot") < 0); CHECK(cif::icompare(std::string_view{ "aap" }, std::string_view{ "noot" }) < 0); } // -------------------------------------------------------------------- TEST_CASE("text_2") { // Test based on https://www.iucr.org/resources/cif/spec/version1.1/semantics // There is a problem with this specification though, the fourth example // as given on the website consists of three lines, the first being blank. auto f = R"( #\ data_X # Here is another example of folding. The following three text fields would be equivalent: loop_ _cat.f1 ;C:\foldername\filename ; ;\ C:\foldername\filename ; ;\ C:\foldername\file\ name ; # but the next example would be a two-line value where the first line had the value "C:\foldername\file\" and the second had the value "name": ; C:\foldername\file\ name ; )"_cf; auto &db = f.front(); auto &cat = db["cat"]; for (size_t ix = 0; std::string v : cat.rows("f1")) { if (++ix == 4) CHECK(v == R"( C:\foldername\file\ name)"); else CHECK(v == R"(C:\foldername\filename)"); } } // -------------------------------------------------------------------- TEST_CASE("from_chars_1") { auto f = R"(data_TEST # loop_ _test.v 616.487 616.487000 )"_cf; auto &db = f.front(); auto &c = db.front(); auto r1 = c.front(); CHECK(r1.get("v") == 616.487); CHECK(r1["v"].compare(616.487) == 0); auto r2 = c.back(); CHECK(r2.get("v") == 616.487); CHECK(r2["v"].compare(616.487) == 0); } // -------------------------------------------------------------------- TEST_CASE("id_1") { CHECK(cif::cif_id_for_number(0) == "A"); CHECK(cif::cif_id_for_number(25) == "Z"); CHECK(cif::cif_id_for_number(26) == "AA"); CHECK(cif::cif_id_for_number(26 + 1) == "AB"); CHECK(cif::cif_id_for_number(26 + 26 * 26 - 1) == "ZZ"); CHECK(cif::cif_id_for_number(26 + 26 * 26) == "AAA"); CHECK(cif::cif_id_for_number(26 + 26 * 26 + 1) == "AAB"); std::set testset; for (int i = 0; i < 100000; ++i) { std::string id = cif::cif_id_for_number(i); CHECK(testset.count(id) == 0); testset.insert(id); } CHECK(testset.size() == 100000); } // -------------------------------------------------------------------- TEST_CASE("cc_1") { std::tuple tests[] = { { "1.0", 1.0f, 0 }, { "1.0e10", 1.0e10f, 0 }, { "-1.1e10", -1.1e10f, 0 }, { "-.2e11", -.2e11f, 0 }, { "1.3e-10", 1.3e-10f, 0 }, { "1.0 ", 1.0f, ' ' }, { "1.0e10 ", 1.0e10f, ' ' }, { "-1.1e10 ", -1.1e10f, ' ' }, { "-.2e11 ", -.2e11f, ' ' }, { "1.3e-10 ", 1.3e-10f, ' ' }, { "3.0", 3.0f, 0 }, { "3.0 ", 3.0f, ' ' }, { "3.000000", 3.0f, 0 }, { "3.000000 ", 3.0f, ' ' }, }; for (const auto &[txt, val, ch] : tests) { float tv; const auto &[ptr, ec] = cif::from_chars(txt.data(), txt.data() + txt.length(), tv); CHECK(ec == std::errc{}); CHECK(tv == val); if (ch != 0) CHECK(*ptr == ch); } } TEST_CASE("cc_2") { std::tuple tests[] = { { 1.1f, 1, "1.1" } }; for (const auto &[val, prec, test] : tests) { char buffer[64] = {}; const auto &[ptr, ec] = std::to_chars(buffer, buffer + sizeof(buffer), val, std::chars_format::fixed, prec); CHECK(ec == std::errc{}); // This line generates a linker error on Windows: // CHECK(buffer == test); CHECK(std::string{ buffer } == std::string{ test }); } } TEST_CASE("item_0") { cif::item i1("v1", "tekst"); cif::item i2("v2", 2); cif::item i3("v3", { 3.0, 2 }); // cif::item i4("v4", true); cif::item i5("v5", nullptr); static_assert(cif::IntegralType); static_assert(cif::FloatType); CHECK(i1.value().get() == "tekst"); CHECK(i2.value().get() == 2); CHECK(i3.value().get() == 3.0); CHECK(i3.value().str() == "3.00"); // CHECK(i4.value().get() == true); CHECK(i5.value().is_null()); CHECK(i5.value().is_missing()); CHECK(i5.value().empty()); i2.value() = false; CHECK(i2.value().type() == cif::item_value_type::INT); // CHECK(i2.value().get() == false); cif::item i6 = std::move(i1); CHECK(i6.value().get() == "tekst"); CHECK(i1.value().is_null()); // NOLINT } // TEST_CASE("row_1") // { // cif::row r; // r.append(0, 1); // r.append(1, "twee"); // cif::category cat("cat"); // cat.add_item("een"); // cat.add_item("twee"); // cif::row_handle rh(cat, r); // const auto &[a, b] = rh.get("een", "twee"); // 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") { cif::category c("foo"); c.emplace({ { "f-1", 1 }, { "f-2", "-1" }, { "f-3", "+1" }, { "f-4", " 1" }, { "f-5", " +1" }, { "f-6", "1 " }, }); auto row = c.front(); CHECK(row["f-1"].get() == 1); CHECK(row["f-2"].get() == -1); 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; item i1("1", "1"); item i2("2", 2.0f); item i3("3", '3'); item ci1(i1); item ci2(i2); item ci3(i3); 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)); CHECK(i1.value() == mi1.value()); CHECK(i2.value() == mi2.value()); CHECK(i3.value() == mi3.value()); // NOLINTBEGIN(bugprone-use-after-move) CHECK(ci1.empty()); CHECK(ci2.empty()); CHECK(ci3.empty()); // NOLINTEND(bugprone-use-after-move) } TEST_CASE("item_2") { using namespace cif; cif::item i0("test1"); CHECK(i0.value().empty()); CHECK(i0.value().type() == cif::item_value_type::MISSING); 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.0f); // TODO: revive/fix // 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"); } // -------------------------------------------------------------------- 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 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(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; 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 } 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') } }); } } 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" } }); int n = 1; const char *ts[] = { "aap", "noot", "mies" }; for (auto r : c) { CHECK(r["id"].get() == n); CHECK(r["s"].compare(ts[n - 1]) == 0); ++n; } n = 1; for (auto r : c) { int i; std::string s; cif::tie(i, s) = r.get("id", "s"); CHECK(i == n); CHECK(s.compare(ts[n - 1]) == 0); ++n; } n = 1; 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" } }; cif::category c("foo"); for (const auto &[id, s] : D) c.emplace({ { "id", id }, { "s", s } }); CHECK(not c.empty()); CHECK(c.size() == 3); cif::category c2(c); CHECK(not c2.empty()); CHECK(c2.size() == 3); cif::category c3(std::move(c)); CHECK(not c3.empty()); CHECK(c3.size() == 3); CHECK(c.empty()); // NOLINT(bugprone-use-after-move) CHECK(c.size() == 0); c = c3; CHECK(not c.empty()); CHECK(c.size() == 3); c = std::move(c2); CHECK(not c.empty()); CHECK(c.size() == 3); } TEST_CASE("c_3") { std::tuple D[] = { { 1, "aap" }, { 2, "noot" }, { 3, "mies" } }; cif::category c("foo"); for (const auto &[id, s] : D) c.emplace({ { "id", id }, { "s", s } }); cif::category c2("bar"); for (auto r : c) c2.emplace(r); // CHECK(c == c2); } 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" } }); 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; CHECK(i1 == i2); CHECK(i1 == i3); CHECK(i1 == i4); CHECK(i1 == i5); } // TEST_CASE("os_1") // { // using namespace cif::literals; // using namespace std::literals; // std::tuple D[] = { // { 1, "aap" }, // { 2, "noot" }, // { 3, "mies" } // }; // cif::category c("foo"); // for (const auto &[id, s] : D) // c.emplace({ { "id", id }, { "s", s } }); // for (auto rh : c) // { // rh["o"].os(1, ',', 2, ": ", rh.get("s")); // } // for (const auto &[id, s] : D) // { // auto rh = c.find1("id"_key == id); // CHECK(rh.get("id") == id); // CHECK(rh.get("s") == s); // CHECK(rh.get("o") == "1,2: "s + s); // } // } // -------------------------------------------------------------------- TEST_CASE("get_1") { auto f = R"(data_TEST # loop_ _test.id _test.name 1 aap 2 noot 3 mies 4 ? 5 . )"_cf; 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); for (auto r : f.front()["test"]) { int id; std::optional name; cif::tie(id, name) = r.get("id", "name"); switch (id) { case 1: REQUIRE(name.has_value()); CHECK(*name == "aap"); // NOLINT break; case 2: REQUIRE(name.has_value()); CHECK(*name == "noot"); // NOLINT break; case 3: REQUIRE(name.has_value()); CHECK(*name == "mies"); // NOLINT break; default: CHECK(name.has_value() == 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; CHECK(f.empty() == false); CHECK(f.size() == 1); auto &db = f.front(); CHECK(db.name() == "TEST"); auto &test = db["test"]; CHECK(test.size() == 3); const char *ts[] = { "aap", "noot", "mies" }; int n = 1; for (const auto &[i, s] : test.rows("id", "name")) { CHECK(i == n); CHECK(s.compare(ts[n - 1]) == 0); ++n; } auto n2 = test.erase(cif::key("id") == 1, [](cif::row_handle r) { CHECK(r["id"].get() == 1); CHECK(r["name"].get() == "aap"); }); CHECK(n2 == 1); // for (auto r: test) // test.erase(r); test.clear(); CHECK(test.empty()); // fill again. 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; auto &db = f.front(); CHECK(db.name() == "TEST"); auto &test = db["test"]; CHECK(test.size() == 3); 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 t = test.find(cif::key("id") == 1); REQUIRE(not t.empty()); CHECK(t.front()["name"].get() == "aap"); auto t2 = test.find(cif::key("value") == 1.2); REQUIRE(not t2.empty()); CHECK(t2.front()["name"].get() == "mies"); } TEST_CASE("ut3") { using namespace cif::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 4 boom . 5 roos ? )"_cf; auto &db = f.front(); CHECK(db.name() == "TEST"); 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"]; CHECK(cat.find_first(cif::key("value") == 1.0, "id") == 1); } // -------------------------------------------------------------------- TEST_CASE("sw_1") { using namespace cif::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 &db = f.front(); auto &test = db["test"]; swap(test.front()["name"], test.back()["name"]); CHECK(test.find1("id"_key == 1, "name") == "mies"); CHECK(test.find1("id"_key == 3, "name") == "aap"); swap(test.front()["name"], test.back()["name"]); CHECK(test.find1("id"_key == 1, "name") == "aap"); CHECK(test.find1("id"_key == 3, "name") == "mies"); } // -------------------------------------------------------------------- TEST_CASE("d0") { auto v = cif::validator_factory::instance().get("mmcif_pdbx.dic"); CHECK(v != nullptr); } // -------------------------------------------------------------------- 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); f.front().set_validator(&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); // } SECTION("three") { auto &cat2 = f.front()["cat_2"]; cat2.emplace({ { "id", "4" }, { "parent_id", "1" }, { "name", "Brulaap" } }); } } // -------------------------------------------------------------------- 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); f.front().set_validator(&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); f.front().set_validator(&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 save_ save__cat_2.parent_id2 _item.name '_cat_2.parent_id2' _item.category_id cat_2 _item.mandatory_code no save_ save__cat_2.parent_id3 _item.name '_cat_2.parent_id3' _item.category_id cat_2 _item.mandatory_code no 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); f.front().set_validator(&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 save_ save__cat_2.parent_id2 _item.name '_cat_2.parent_id2' _item.category_id cat_2 _item.mandatory_code no save_ save__cat_2.parent_id3 _item.name '_cat_2.parent_id3' _item.category_id cat_2 _item.mandatory_code no 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); f.front().set_validator(&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); std::vector CRids; std::ranges::transform(CR2set, std::back_inserter(CRids), [](cif::row_handle r) { return r["id"].get(); }); std::ranges::sort(CRids); 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 save_ save__cat_2.parent_id_2 _item.name '_cat_2.parent_id_2' _item.category_id cat_2 _item.mandatory_code no 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); f.front().set_validator(&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"); REQUIRE(v.has_value()); CHECK(*v == 1); // NOLINT 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 mmcif_pdbx.dic 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); f.front().set_validator(&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); f.front().set_validator(&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("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 ?? )"_cf; auto &db1 = data1.front(); auto &test1 = db1["test"]; CHECK(test1.size() == 1); for (auto r : test1) { auto text = r.get("text"); CHECK(text == "??"); } std::stringstream ss; data1.save(ss); auto data2 = cif::file(ss); auto &db2 = data2.front(); auto &test2 = db2["test"]; 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) { 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); f.front().set_validator(&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); f.front().set_validator(&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); f.front().set_validator(&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 }, { "id_2", nullptr } }, 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: REQUIRE(name.has_value()); CHECK(*name == "aap"); // NOLINT break; case 2: REQUIRE(name.has_value()); CHECK(*name == "noot"); // NOLINT break; case 3: REQUIRE(name.has_value()); CHECK(*name == "mies"); // NOLINT 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 // 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; auto &db = f.front(); auto &cat = db[""]; CHECK(not cat.empty()); auto r = cat.front(); CHECK(r["version"].get() == 1.0); CHECK(r["date"].get() == "today"); std::stringstream ss; ss << db; auto f2 = cif::file(ss); auto &db2 = f2.front(); CHECK(db == db2); } 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 &db = f.front(); auto &test = db["test"]; auto v = test.find1>("id"_key == 1, "value"); CHECK(v.has_value()); CHECK(*v == 1.0f); // NOLINT(bugprone-unchecked-optional-access) v = test.find1>("id"_key == 4, "value"); CHECK(v.has_value() == false); } // -------------------------------------------------------------------- TEST_CASE("compound_test_1") { cif::compound_factory::instance().push_dictionary(gTestDir / "REA_v2.cif"); auto compound = cif::compound_factory::instance().create("REA_v2"); CHECK(compound != nullptr); CHECK(cif::iequals(compound->id(), "REA_v2")); } // -------------------------------------------------------------------- TEST_CASE("pdb_parser_test_1") { char k1CBS[] = R"(HEADER RETINOIC-ACID TRANSPORT 28-SEP-94 1CBS TITLE CRYSTAL STRUCTURE OF CELLULAR RETINOIC-ACID-BINDING TITLE 2 PROTEINS I AND II IN COMPLEX WITH ALL-TRANS-RETINOIC ACID TITLE 3 AND A SYNTHETIC RETINOID COMPND MOL_ID: 1; COMPND 2 MOLECULE: CELLULAR RETINOIC ACID BINDING PROTEIN TYPE II; COMPND 3 CHAIN: A; COMPND 4 ENGINEERED: YES SOURCE MOL_ID: 1; SOURCE 2 ORGANISM_SCIENTIFIC: HOMO SAPIENS; SOURCE 3 ORGANISM_COMMON: HUMAN; SOURCE 4 ORGANISM_TAXID: 9606; SOURCE 5 CELL_LINE: BL21; SOURCE 6 GENE: HUMAN CRABP-II; SOURCE 7 EXPRESSION_SYSTEM: ESCHERICHIA COLI BL21(DE3); SOURCE 8 EXPRESSION_SYSTEM_TAXID: 469008; SOURCE 9 EXPRESSION_SYSTEM_STRAIN: BL21 (DE3); SOURCE 10 EXPRESSION_SYSTEM_PLASMID: PET-3A KEYWDS RETINOIC-ACID TRANSPORT EXPDTA X-RAY DIFFRACTION AUTHOR G.J.KLEYWEGT,T.BERGFORS,T.A.JONES ATOM 1 N PRO A 1 16.979 13.301 44.555 1.00 30.05 N ATOM 2 CA PRO A 1 18.150 13.525 43.680 1.00 28.82 C ATOM 3 C PRO A 1 18.656 14.966 43.784 1.00 26.59 C ATOM 4 O PRO A 1 17.890 15.889 44.078 1.00 26.84 O ATOM 5 CB PRO A 1 17.678 13.270 42.255 1.00 29.24 C ATOM 6 CG PRO A 1 16.248 13.734 42.347 1.00 29.29 C ATOM 7 CD PRO A 1 15.762 13.216 43.724 1.00 30.71 C)"; struct membuf : public std::streambuf { membuf(char *text, std::size_t length) { this->setg(text, text, text + length); } } buffer(k1CBS, sizeof(k1CBS) - 1); std::istream is(&buffer); auto f = cif::pdb::read(is); CHECK(f.is_valid()); } // -------------------------------------------------------------------- TEST_CASE("compound_not_found_test_1") { auto cmp = cif::compound_factory::instance().create("&&&"); CHECK(cmp == nullptr); } // -------------------------------------------------------------------- // PDB2CIF tests TEST_CASE("pdb2cif_formula_weight") { cif::compound_factory::instance().push_dictionary(gTestDir / "REA.cif"); cif::file a = cif::pdb::read(gTestDir / "pdb1cbs.ent.gz"); auto fw = a.front()["entity"].find1(cif::key("id") == 1, "formula_weight"); CHECK(std::abs(fw - 15581.802f) < 0.1f); fw = a.front()["entity"].find1(cif::key("id") == 2, "formula_weight"); CHECK(fw == 300.435f); fw = a.front()["entity"].find1(cif::key("id") == 3, "formula_weight"); CHECK(fw == 18.015f); } // // -------------------------------------------------------------------- // TEST_CASE("update_values_with_provider") // { // } // -------------------------------------------------------------------- TEST_CASE("io-test-1") { cif::category cat("test"); cat.emplace({ { "v1", { 0.0, 3 } } }); std::ostringstream os; os << cat; CHECK(os.str() == R"(_test.v1 0.000 # )"); } TEST_CASE("io-test-2") { auto data = R"(data_test loop_ _test.v1 1.000 0.000 )"_cf; std::ostringstream os; os << data; CHECK(os.str() == R"(data_test # loop_ _test.v1 1.000 0.000 # )"); } TEST_CASE("io-test-3") { auto data = R"(data_test loop_ _atom_site.group_PDB _atom_site.id _atom_site.type_symbol _atom_site.label_atom_id _atom_site.label_alt_id _atom_site.label_comp_id _atom_site.label_asym_id _atom_site.label_entity_id _atom_site.label_seq_id _atom_site.pdbx_PDB_ins_code _atom_site.Cartn_x _atom_site.Cartn_y _atom_site.Cartn_z _atom_site.occupancy _atom_site.B_iso_or_equiv _atom_site.pdbx_formal_charge _atom_site.auth_atom_id _atom_site.auth_comp_id _atom_site.auth_seq_id _atom_site.auth_asym_id _atom_site.pdbx_PDB_model_num HETATM 1 O O . HOH A 1 . ? 10.518 -1.781 0 1 37.22 ? O HOH 1 D 1 )"_cf; auto &db = data.front(); db.load_dictionary("mmcif_pdbx.dic"); auto row = db["atom_site"].front(); const auto &[x, y, z, id, label_asym_id, auth_asym_id, auth_seq_id] = row.get("cartn_x", "cartn_y", "cartn_z", "id", "label_asym_id", "auth_asym_id", "auth_seq_id"); cif::mm::structure s(data); s.create_water(row); std::ostringstream os; os << db["atom_site"]; CHECK(os.str() == R"(loop_ _atom_site.group_PDB _atom_site.id _atom_site.type_symbol _atom_site.label_atom_id _atom_site.label_alt_id _atom_site.label_comp_id _atom_site.label_asym_id _atom_site.label_entity_id _atom_site.label_seq_id _atom_site.pdbx_PDB_ins_code _atom_site.Cartn_x _atom_site.Cartn_y _atom_site.Cartn_z _atom_site.occupancy _atom_site.B_iso_or_equiv _atom_site.pdbx_formal_charge _atom_site.auth_atom_id _atom_site.auth_comp_id _atom_site.auth_seq_id _atom_site.auth_asym_id _atom_site.pdbx_PDB_model_num HETATM 1 O O . HOH A 1 . ? 10.518 -1.781 0 1 37.22 ? O HOH 1 D 1 HETATM 2 O O . HOH A 1 . ? 10.518 -1.781 0 1 37.22 ? O HOH 2 D 1 # )"); } // -------------------------------------------------------------------- TEST_CASE("number-test-1") { auto data = R"(data_test _pdbx_contact_author.id 1 _pdbx_contact_author.name_mi +98765432109 )"_cf; auto &db = data.front(); db.load_dictionary("mmcif_pdbx.dic"); auto r = db["pdbx_contact_author"].front(); CHECK(r["name_mi"].str() == "+98765432109"); CHECK(r["name_mi"].get() == 98765432109); CHECK(r["name_mi"].get() == 98765432109.0); } TEST_CASE("number-test-2") { auto data = R"(data_test _pdbx_contact_author.id 1 _pdbx_contact_author.name_mi '+98765432109' )"_cf; auto &db = data.front(); db.load_dictionary("mmcif_pdbx.dic"); auto r = db["pdbx_contact_author"].front(); CHECK(r["name_mi"].str() == "+98765432109"); CHECK(r["name_mi"].get() == 98765432109); CHECK(r["name_mi"].get() == 98765432109.0); }// -------------------------------------------------------------------- TEST_CASE("q-1") { auto data = R"(data_test _test.s ;1234567890 1234567890 ; )"_cf; auto r = data.front()["test"].find(cif::key("s") == "1234567890\n1234567890"); CHECK(r.size() == 1); } TEST_CASE("large-int-1") { auto data = R"(data_test _entry.id 82E4475FF8B27F36 )"_cf; auto &db = data.front(); db.load_dictionary("mmcif_pdbx.dic"); auto r = db["entry"].front(); CHECK(r["id"].get() == "82E4475FF8B27F36"); }