Repository: biopython/biopython

Issue:
Entrez doesn't yet support ECitMatch
http://www.ncbi.nlm.nih.gov/books/NBK25499/#chapter4.ECitMatch



Discussion:
Interesting "_Retrieves PubMed IDs (PMIDs) that correspond to a set of input citation strings._", so we want a new `ecitmatch(...)` function.

I wonder if we should off load the input string formatting entirely to the user (pipe separated fields)?

Yep, writing it now. I'm writing it as accepting a list of dicts, is that acceptable?

The reason I'd taken on formatting (and somewhat breaking tradition with Entrez module) is that the formatting is quite unpleasant here. Pipe separated fields? Multiple citations joined on a `%0D`? I figured it made sense to handle formatting and provide end users with a nicer interface.

Hmm. I think we should allow a plain string (pre-formatted as per NCBI expectations), so that people can do it themselves if they want to, as per existing `Bio.Entrez` design.

I'm open to something more user friendly as well, maybe a (list of) NamedTuples? What do you think @mdehoon?


Resolving patch:
diff --git a/Bio/Entrez/__init__.py b/Bio/Entrez/__init__.py
--- a/Bio/Entrez/__init__.py
+++ b/Bio/Entrez/__init__.py
@@ -39,6 +39,8 @@
     - egquery      Provides Entrez database counts in XML for a single search
       using Global Query.
     - espell       Retrieves spelling suggestions.
+    - ecitmatch    Retrieves PubMed IDs (PMIDs) that correspond to a set of
+      input citation strings.
 
     - read         Parses the XML results returned by any of the above functions.
       Typical usage is:
@@ -351,6 +353,49 @@ def espell(**keywds):
     return _open(cgi, variables)
 
 
+def ecitmatch(**keywds):
+    """ECitMatch retrieves PMIDs-Citation linking
+
+    ECitMatch retrieves PubMed IDs (PMIDs) that correspond to a set of input citation strings.
+
+    See the online documentation for an explanation of the parameters:
+    http://www.ncbi.nlm.nih.gov/books/NBK25499/#chapter4.ECitMatch
+
+    Return a handle to the results, by default in plain text
+
+    Raises an IOError exception if there's a network error.
+
+    Short example:
+
+    >>> from Bio import Entrez
+    >>> Entrez.email = "Your.Name.Here@example.org"
+    >>> citation_1 = {
+    ...    "journal_title": "proc natl acad sci u s a",
+    ...    "year": "1991", "volume": "88", "first_page": "3248",
+    ...    "author_name": "mann bj", "key": "citation_1"}
+    >>> record = Entrez.ecitmatch(db="pubmed", bdata=[citation_1])
+    >>> print(record["Query"])
+    """
+    cgi = 'http://eutils.ncbi.nlm.nih.gov/entrez/eutils/ecitmatch.cgi'
+    # XML is the only supported value, and it actually returns TXT.
+    variables = {'retmode': 'xml'}
+    citation_keys = ('journal_title', 'year', 'volume', 'first_page', 'author_name', 'key')
+
+    # Accept pre-formatted strings
+    if isinstance(keywds['bdata'], str):
+        variables.update(keywds)
+    else:
+        # Alternatively accept a nicer interface
+        variables['db'] = keywds['db']
+        bdata = []
+        for citation in keywds['bdata']:
+            formatted_citation = '|'.join([citation.get(key, "") for key in citation_keys])
+            bdata.append(formatted_citation)
+        variables['bdata'] = '\r'.join(bdata)
+
+    return _open(cgi, variables, ecitmatch=True)
+
+
 def read(handle, validate=True):
     """Parses an XML file from the NCBI Entrez Utilities into python objects.
 
@@ -409,7 +454,7 @@ def parse(handle, validate=True):
     return records
 
 
-def _open(cgi, params=None, post=False):
+def _open(cgi, params=None, post=False, ecitmatch=False):
     """Helper function to build the URL and open a handle to it (PRIVATE).
 
     Open a handle to Entrez.  cgi is the URL for the cgi script to access.
@@ -457,6 +502,9 @@ def _open(cgi, params=None, post=False):
 E-utilities.""", UserWarning)
     # Open a handle to Entrez.
     options = _urlencode(params, doseq=True)
+    # _urlencode encodes pipes, which NCBI expects in ECitMatch
+    if ecitmatch:
+        options = options.replace('%7C', '|')
     # print cgi + "?" + options
     try:
         if post:


Test patch:
diff --git a/Tests/test_Entrez_online.py b/Tests/test_Entrez_online.py
--- a/Tests/test_Entrez_online.py
+++ b/Tests/test_Entrez_online.py
@@ -152,6 +152,18 @@ def test_epost(self):
         self.assertEqual(URL_HEAD + "epost.fcgi", handle.url)
         handle.close()
 
+    def test_ecitmatch(self):
+        citation = {
+            "journal_title": "proc natl acad sci u s a",
+            "year": "1991", "volume": "88", "first_page": "3248",
+            "author_name": "mann bj", "key": "citation_1"
+        }
+        handle = Entrez.ecitmatch(db="pubmed", bdata=[citation])
+        self.assertTrue("retmode=xml" in handle.url, handle.url)
+        result = handle.read()
+        expected_result = "proc natl acad sci u s a|1991|88|3248|mann bj|citation_1|2014248\n"
+        self.assertEquals(result, expected_result)
+
 
 if __name__ == "__main__":
     runner = unittest.TextTestRunner(verbosity=2)