From a3f3f3ab842dd8608f2d5197258117dc482a7972 Mon Sep 17 00:00:00 2001
From: Gerardo Tauriello <gerardo.tauriello@unibas.ch>
Date: Tue, 9 Jul 2019 17:23:36 +0200
Subject: [PATCH] SCHWED-4246: Add mol.QueryQuoteName function.

---
 modules/mol/base/pymod/export_query.cc |  2 +-
 modules/mol/base/src/query.hh          | 19 +++++++++++
 modules/mol/base/tests/test_query.cc   | 46 +++++++++++++++++++++++++-
 3 files changed, 65 insertions(+), 2 deletions(-)

diff --git a/modules/mol/base/pymod/export_query.cc b/modules/mol/base/pymod/export_query.cc
index 7930cd426..8f1fc9460 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 c76454670..d855703c3 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 57fc557bf..5f8865ae5 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();
-- 
GitLab