diff --git a/modules/mol/base/pymod/export_query.cc b/modules/mol/base/pymod/export_query.cc index 7930cd4260d5f5a335aa5b5c5fc68da405f0de43..8f1fc94605e6beed10d61b0f65f94733fcd05f78 100644 --- a/modules/mol/base/pymod/export_query.cc +++ b/modules/mol/base/pymod/export_query.cc @@ -68,5 +68,5 @@ void export_Query() return_value_policy<copy_const_reference>()) ; - + def("QueryQuoteName", &QueryQuoteName); } diff --git a/modules/mol/base/src/query.hh b/modules/mol/base/src/query.hh index c76454670162e200696f62f6d9a7f17b422d4f20..d855703c3e55f3e366bbda51b8c6dc79e309e114 100644 --- a/modules/mol/base/src/query.hh +++ b/modules/mol/base/src/query.hh @@ -117,6 +117,25 @@ private: impl::QueryImplP impl_; }; +// inlined helper function to quote strings for use in queries (e.g. cname=..). +// throws Error if string cannot be quoted +inline String DLLEXPORT_OST_MOL QueryQuoteName(const String& name) { + // check what quotation marks to use + char quote = '\''; + if (name.find('\'') != String::npos) { + if (name.find('"') != String::npos) { + throw Error("Cannot quote chain name " + name + " because it contains '" + " and \" in its name."); + } + quote = '"'; + } + // check problematic \ at end (escapes quotation mark and breaks logic) + if (name[name.length() - 1] == '\\') { + throw Error("Cannot quote chain name " + name + "because it ends in \\."); + } + return quote + name + quote; +} + }} // ns #endif diff --git a/modules/mol/base/tests/test_query.cc b/modules/mol/base/tests/test_query.cc index 57fc557bfd58947ea592d013b609ebc91650fa60..5f8865ae5b71d48e0247615b106ce34525f192bf 100644 --- a/modules/mol/base/tests/test_query.cc +++ b/modules/mol/base/tests/test_query.cc @@ -286,7 +286,7 @@ BOOST_AUTO_TEST_CASE(test_query_throw) BOOST_CHECK_NO_THROW(e.Select("gcnotsetprop:0=1")); } -BOOST_AUTO_TEST_CASE(test_glob) +BOOST_AUTO_TEST_CASE(test_glob) { EntityHandle e=make_query_test_entity(); ensure_counts(e, "rname=MET and aname=C*", 1, 1, 5); @@ -299,4 +299,48 @@ BOOST_AUTO_TEST_CASE(test_glob) ensure_counts(e, "rname=LEU and aname=?D?", 1, 1, 2); } +BOOST_AUTO_TEST_CASE(test_quoting) +{ + // possible letters taken from mmCIF dictionary + // -> http://mmcif.wwpdb.org/dictionaries/mmcif_pdbx_v50.dic/Items/_atom_site.label_asym_id.html + // not ok for us: '\' alone + String single_letters = " ][_,.;:\"&<>()/{}'`~!@#$\%|+-" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh" + "ijklmnopqrstuvwxyz"; + // put into vector + std::vector<String> chain_names; + for (size_t i = 0; i < single_letters.length(); ++i) { + chain_names.push_back(String(1, single_letters[i])); + } + // add some multi letter names and empty chain name too + // not ok: mixing ' and ", '\' at end + chain_names.push_back("\\ ][_,.;:&<>()/{}'`~!@#$\%|+-"); + chain_names.push_back("ABCDEFGHIJKLMNOPQRSTU'VWXYZ0123456789abcdefgh"); + chain_names.push_back("ijklmno\"pqrstuvwxyz"); + chain_names.push_back(""); + // setup entity + EntityHandle ent = CreateEntity(); + XCSEditor edi = ent.EditXCS(); + for (size_t i = 0; i < chain_names.size(); ++i) { + edi.InsertChain(chain_names[i]); + } + // test quoting + String query_all = "cname="; + for (size_t i = 0; i < chain_names.size(); ++i) { + const String quoted_name = QueryQuoteName(chain_names[i]); + BOOST_CHECK_EQUAL(ent.Select("cname=" + quoted_name).GetChainCount(), 1); + query_all += quoted_name; + if (i != chain_names.size() - 1) query_all += ","; + } + BOOST_CHECK_EQUAL(ent.Select(query_all).GetChainCount(), + int(chain_names.size())); + // note: quoting * keeps it as wild card! + BOOST_CHECK_EQUAL(ent.Select("cname=" + QueryQuoteName("*")).GetChainCount(), + int(chain_names.size())); + // expected failures (mixing ' and ", '\' at end) + BOOST_CHECK_THROW(QueryQuoteName("'\""), Error); + BOOST_CHECK_THROW(QueryQuoteName("\\"), Error); + BOOST_CHECK_THROW(QueryQuoteName("a\\"), Error); +} + BOOST_AUTO_TEST_SUITE_END();