# -*- python -*-
#
#  This file is part of bioservices software
#
#  Copyright (c) 2011-2013 - EBI-EMBL
#
#  File author(s): 
#      Thomas Cokelaer <cokelaer@ebi.ac.uk>
#      https://www.assembla.com/spaces/bioservices/team
#
#  Distributed under the GPLv3 License.
#  See accompanying file LICENSE.txt or copy at
#      http://www.gnu.org/licenses/gpl-3.0.html
#
#  website: https://www.assembla.com/spaces/bioservices/wiki
#  documentation: http://packages.python.org/bioservices
#
##############################################################################
#$Id: kegg.py 220 2013-06-13 16:47:12Z cokelaer $
"""This module provides a class :class:`~Kegg` to access to the
REST Kegg interface. There are additional methods and functionalities added by 
**BioServices**.

.. note:: a previous imterface to the KEGG WSDL service was designed but the
    WSDL closed in  Dec 2012.

.. topic:: What is KEGG ?

    :URL: http://www.kegg.jp/
    :REST: http://www.kegg.jp/kegg/rest/keggapi.html
    :weblink: http://www.genome.jp/kegg/rest/weblink.html
    :dbentries: http://www.genome.jp/kegg/rest/dbentry.html

    .. highlights::

        "KEGG is a database resource for understanding high-level functions and
        utilities of the biological system, such as the cell, the organism and the
        ecosystem, from molecular-level information, especially large-scale molecular
        datasets generated by genome sequencing and other high-throughput experimental
        technologies (See Release notes for new and updated features). "

        -- Kegg home page, Jan 2013

.. _terminology:

Some terminology
--------------------

The following list is a simplified list of terminology taken from Kegg API
pages.


* organisms (**org**) are made of a three-letter (or four-letter) code (e.g.,
  **hsa** stands for Human Sapiens) used in KEGG (see  :attr:`~bioservices.kegg.Kegg.organismIds`). 
* **db** is a database name. See :attr:`~bioservices.kegg.Kegg.databases`
  attribute and :ref:`kegg_database` section.
* **entry_id** is a unique identifier. It is a combination of the database name
  and the identifier of an entry joined by a colon sign (e.g. 'embl:J00231').

  Here are some examples of entry Ids:

    * **genes_id**: A KEGG organism and a gene name (e.g. 'eco:b0001').
    * **enzyme_id**: 'ec' and an enzyme code. (e.g. 'ec:1.1.1.1'). See :attr:`~bioservices.kegg.Kegg.enzymeIds`.
    * **compound_id**: 'cpd' and a compound number (e.g. 'cpd:C00158'). 
      Some compounds also have 'glycan_id' and
      both IDs are accepted and converted internally.
      See :attr:`~bioservices.kegg.Kegg.compoundIds`.
    * **drug_id**: 'dr' and a drug number (e.g. 'dr:D00201'). See
      :attr:`~bioservices.kegg.Kegg.drugIds`.
    * **glycan_id**: 'gl' and a glycan number (e.g.
    * 'gl:G00050'). Some glycans also have 'compound_id' and both
      IDs are accepted and converted internally. see
      :attr:`~bioservices.kegg.Kegg.glycanIds` attribute.
    * **reaction_id**:  'rn' and a reaction number (e.g.
    * 'rn:R00959' is a reaction which catalyze cpd:C00103 into cpd:C00668).
      See :attr:`~bioservices.kegg.Kegg.reactionIds` attribute.
    * **pathway_id**: 'path' and a pathway number. Pathway numbers prefixed 
      by 'map' specify the reference pathway and pathways prefixed by 
      a KEGG organism specify pathways specific to the organism (e.g. 
      'path:map00020', 'path:eco00020'). See :attr:`~bioservices.kegg.Kegg.pathwayIds` attribute.
    * **motif_id**: a motif database names ('ps' for prosite, 'bl' for blocks, 
      'pr' for prints, 'pd' for prodom, and 'pf' for pfam) and a motif entry
      name. (e.g. 'pf:DnaJ' means a Pfam  database entry 'DnaJ').
    * **ko_id**: identifier made of 'ko' and a ko number (e.g. 'ko:K02598').
      See :attr:`~bioservices.kegg.Kegg.koIds` attribute.


.. _kegg_database:

Kegg Databases Names and Abbreviations
-------------------------------------------

Here is a list of databases used in KEGG API with their name and abbreviation:

=============== =========== ==========
Database Name   Abbrev      kid       
=============== =========== ==========
pathway         path        map number  
brite           br          br number   
module          md          M number    
disease         ds          H number  
drug            dr          D number  
environ         ev          E number 
orthology       ko          K number    
genome          genome      T number    
genomes         gn          T number
genes           -           -      
ligand          ligand      -     
compound        cpd         C number 
glycan          gl          G number    
reaction        rn          R number    
rpair           rp          RP number   
rclass          rc          RC number   
enzyme          ec          -   
=============== =========== ========== 


.. _db_entries:

Database Entries
-------------------

Database entries can be written in on of the following ways::

    <dbentries> = <dbentry>1[+<dbentry>2...]
    <dbentry> = <db:entry> | <kid> | <org:gene>

Each database entry is identified by::

    db:entry 

where "db" is the database name or its abbreviation shown above and  "entry" is the entry name or the accession number that is uniquely assigned within the database. In reality "db" may be omitted, for the entry name called the KEGG object identifier (kid) is unique across KEGG.::

    kid = database-dependent prefix + five-digit number

In the KEGG GENES database the db:entry combination must be specified. This is
more specifically written as::

    org:gene 

where "org" is the three- or four-letter KEGG organism code or the T number genome
identifier and "gene" is the gene identifier, usually locus_tag or ncbi GeneID, or the primary
gene name.


"""
from __future__ import print_function

from bioservices.services import RESTService, BioServicesError
#from services import RESTService, BioServicesError
import webbrowser
import copy


__all__ = ["Kegg", "KeggParser"]

class Kegg(RESTService):
    """Interface to the `KEGG <http://www.genome.jp/kegg/pathway.html>`_ service

    This class provides an interface to the KEGG REST API. The weblink tools 
    are partially accesible. All dbentries can be parsed into dictionaries using
    the :class:`KeggParser`

    Here are some examples. In order to retrieve the entry of the gene identifier 7535 of the 
    **hsa** organism, type::

        from bioservices import Kegg
        s = Kegg()
        print s.get("hsa:7535")
   
    The output is the raw ouput sent by KEGG API. See :class:`KeggParser` to 
    parse this output.
 
    .. seealso:: The :ref:`db_entries` to know more about the db entries format.
 
    Another example here below shows how to print the list of pathways of 
    the human organism::

        print s.list("pathway", organism="hsa")

    Further post processing would allow you to retrieve the pathway Ids. However,
    we provide additional functions to the KEGG API so the previous code and post 
    processing to extract the pathway Ids can be written as::

        s.organism = "hsa"
        s.pathwayIds

    and similarly you can get all :meth:`databases` output and database Ids easily. 
    For example, for the reaction database::

        s.reaction   # equivalent to s.list("reaction")
        s.reactionIds

    Other methods of interest are :meth:`conv`, :meth:`find`, :meth:`get`. We also provide 
    a :meth:`check_dbentries` to check validity of an entry Id.

    .. seealso:: :ref:`kegg_database`, :ref:`db_entries`, :ref:`terminology`.

    """

    #: valid databases
    _valid_DB_base = ["module", "disease", "drug","environ", "ko", 
        "genome", "compound", "glycan", "reaction", "rpair", "rclass", 
        "enzyme"]
    _valid_DB = _valid_DB_base + ["pathway", "brite", "genes", "ligand", \
        "organism", "genomes", "orthology"]
    _valid_databases_info = _valid_DB_base + ["pathway", "brite", "genes",\
        "ligand", "genomes", "kegg"]
    _valid_databases_list = _valid_DB_base + ["pathway", "brite", "organism"]
    _valid_databases_find = _valid_DB_base + ["pathway", "genes", "ligand"] 
    _valid_databases_link = _valid_DB_base + ["pathway", "brite"] 

    _docIds = "\n\n.. seealso:: :meth:`list`\n"
    def __init__(self, verbose=True):
        """.. rubric:: Constructor

        :param bool verbose: prints informative messages

        """
        super(Kegg, self).__init__(name="Kegg", url="http://rest.kegg.jp", verbose=verbose)
        self.easyXMLConversion = False
        self._organism = None

        self._organisms = None
        self._organisms_tnumbers = None
        self._pathway = None
        self._glycan = None
        self._compound = None
        self._ko = None
        self._enzyme = None
        self._reaction = None
        self._drug = None
        self._brite = None

        self._buffer = {}

        #self.keggParser = KeggParser()


    # we could use this to retrieve all databases Ids but 
    def __getattr__(self, req):
        """ for x in s._valid_databases_list: print len(getattr(s, x))"""
        if req.endswith("Ids"):
            db = req[0:-3]
            res = self.list(db)
            if db in ["",""]:
                Ids = [x.split()[1] for x in res.split("\n") if len(x)]
            else:
                Ids = [x.split()[0] for x in res.split("\n") if len(x)]
            return Ids
        elif req in self.databases:
            res = self.list(req)
            return res

    def code2Tnumber(self, code):
        """Converts organism code to its T number

        .. doctest::

            >>> from bioservices import *
            >>> s = Kegg()
            >>> s.code2Tnumber("hsa")
            'T01001'
        """
        index = self.organismIds.index(code)
        return self.organismTnumbers[index]

    def Tnumber2code(self, Tnumber):
        """Converts organism T number to its code

        .. doctest::

            >>> from bioservices import *
            >>> s = Kegg()
            >>> s.Tnumber2code("T01001")
            'hsa'
        """
        index = self.organismTnumbers.index(Tnumber)
        return self.organismIds[index]

    def isOrganism(self, org):
        """Checks if org is a KEGG organism

        :param str org:
        :return: True if org is in the KEGG organism list (code or Tnumber)

        ::

            >>> s.isOrganism("hsa")
            True

        """
        if org in self.organismIds:
            return True
        if org in self.organismTnumbers:
            return True
        else:
            return False

    def _checkDB(self, database=None, mode=None):
        self.logging.info("checking database %s (mode=%s)" % (database, mode))
        isOrg = self.isOrganism(database)

        if mode == "info":
            if database not in Kegg._valid_databases_info and isOrg == False:
                self.logging.error("database or organism provided is not correct (mode=info)")
                raise
        elif mode == "list":
            if database not in Kegg._valid_databases_list and isOrg == False:
                self.logging.error("database provided is not correct (mode=list)")
                raise 
        elif mode == "find":
            if database not in Kegg._valid_databases_find and isOrg == False:
                self.logging.error("database provided is not correct (mode=find)")
                raise 
        elif mode == "link":
            if database not in Kegg._valid_databases_link and isOrg == False:
                self.logging.error("database provided is not correct (mode=link)")
                raise 

        else:
            raise ValueError("mode can be only info, list, ")

    def info(self, database="kegg"):
        """Displays the current statistics of a given database

        :param str database: can be one of: kegg (default), brite, module,
            disease, drug, environ, ko, genome, compound, glycan, reaction,
            rpair, rclass, enzyme, genomes, genes, ligand or any 
            :attr:`organismIds`. 

        ::

            from bioservices import Kegg
            s = Kegg()
            s.info("hsa") # human organism
            s.info("T01001") # same as above
            s.info("pathway")

        """
        url = self.url+"/"+"info"
        self._checkDB(database, mode="info")

        url = url + "/" + database
        res = self.request(url)
        return res

    def list(self, query, organism=None):
        """Returns a list of entry identifiers and associated definition for a given database or a given set of database entries 

        :param str query: can be one of pathway, brite, module,
            disease, drug, environ, ko, genome, compound,
            glycan, reaction, rpair, rclass, enzyme, organism
            **or** an organism from the :attr:`organismIds` attribute **or** a valid
            dbentry (see below). If a dbentry query is provided, organism
            should not be used!
        :param str organism: a valid organism identifier that can be 
            provided. If so, database can be only "pathway" or "module". If 
            not provided, the default value is chosen (:attr:`organism`)
        :return: A string with a structure that depends on the query



        Here is an example that shows how to extract the pathways IDs related to
        the hsa organism::

            >>> s = Kegg()
            >>> res = s.list("pathway", organism="hsa")
            >>> pathways = [x.split()[0] for x in res.strip().split("\\n")]
            >>> len(pathways)  # as of Dec 2012
            261

        Note, however, that there are convenient aliases to some of the databases. 
        For instance, the pathway Ids can also be retrieved as a list from the
        :attr:`pathwayIds` attribute (after defining the :attr:`organism` attribute). 

        .. note:: If you set the query to a valid organism, then the second
               argument rganism is irrelevant and ignored.

        .. note:: If the query is not a database or an organism, it is supposed
            to be a valid dbentries string and the maximum number of entries is 100.

        Other examples::

            s.list("pathway")             # returns the list of reference pathways
            s.list("pathway", "hsa")      # returns the list of human pathways
            s.list("organism")            # returns the list of KEGG organisms with taxonomic classification
            s.list("hsa")                 # returns the entire list of human genes
            s.list("T01001")              # same as above
            s.list("hsa:10458+ece:Z5100") # returns the list of a human gene and an E.coli O157 gene
            s.list("cpd:C01290+gl:G00092")# returns the list of a compound entry and a glycan entry
            s.list("C01290+G00092")       # same as above 
        """
        url = self.url+"/"+"list"
        if query:
            #can be something else than a database so we can not use checkDB
            #self._checkDB(database, mode="list")
            url = url + "/" + query

        if organism:
            if organism not in self.organismIds:
                self.logging.error("""Invalid organism provided (%s). See the organismIds attribute""" % organism)
                raise BioServicesError("Not a valid organism")
            if query not in ["pathway", "module"]:
                self.logging.error("""
    If organism is set, then the first argument
    (database) must be either 'pathway' or 'module'. You provided %s""" % query)
                raise
            url = url + "/" + organism


        res = self.request(url)
        return res

    def find(self, database, query, option=None):
        """finds entries with matching query keywords or other query data in a given database 

        :param str database: can be one of pathway, module, disease, drug,
            environ, ko, genome, compound, glycan, reaction, rpair, rclass, 
            enzyme, genes, ligand or an organism code (see :attr:`organismIds`
            attributes) or T number (see :attr:`organismTnumbers` attribute).
        :param str query: See examples
        :param str option: If option provided, database can be only 'compound' 
            or 'drug'. Option can be 'formula', 'exact_mass' or 'mol_weight'


        .. note:: Keyword search against brite is not supported. Use /list/brite to 
            retrieve a short list.

        ::

            # search for pathways that contain Viral in the definition
            s.find("pathway", "Viral")
            # for keywords "shiga" and "toxin"
            s.find("genes", "shiga+toxin")
            # for keywords "shiga toxin"
            s.find("genes", ""shiga toxin")
            # for chemical formula "C7H10O5"
            s.find("compound", "C7H10O5", "formula")
            # for chemical formula containing "O5" and "C7"
            s.find("compound", "O5C7","formula")
            # for 174.045 =< exact mass < 174.055
            s.find("compound", "174.05","exact_mass")
            # for 300 =< molecular weight =< 310
            s.find("compound", "300-310","mol_weight")

        """
        _valid_options = ["formula", "exact_mass", "mol_weight"]
        _valid_db_options = ["compound", "drug"]

        self._checkDB(database, mode="find") 
        url = self.url + "/find/"+ database + "/" +  query

        if option:
            if database not in _valid_db_options:
                raise ValueError("invalid database. Since option was provided, database must be in %s" % _valid_db_options)
            if option not in _valid_options:
                raise ValueError("invalid option. Must be in %s " % _valid_options)
            url +=  "/" + option

        res = self.request(url)
        return res

    def show_entry(self, entry):
        """Opens URL corresponding to a valid entry

        ::

            s.www_bget("path:hsa05416")

        """
        url = "http://www.kegg.jp/dbget-bin/www_bget?" + entry
        self.logging.info(url)
        import webbrowser
        webbrowser.open(url)

    def get(self, dbentries, option=None):
        """Retrieves given database entries 

        :param str dbentries: KEGG database entries involving the following 
            database: pathway, brite, module, disease, drug, environ, ko, genome
            compound, glycan,  reaction, rpair, rclass, enzyme **or** any organism 
            using the KEGG organism code (see :attr:`organismIds`
            attributes) or T number (see :attr:`organismTnumbers` attribute).
        :param str option: one of: aaseq, ntseq, mol, kcf, image, kgml

      .. note:: you can add the option at the end of dbentries in which case
            the parameter option must not be used (see example)

        ::

            from bioservices import Kegg
            s = Kegg()
            # retrieves a compound entry and a glycan entry
            s.get("cpd:C01290+gl:G00092") 
            # same as above
            s.get("C01290+G00092")  
            # retrieves a human gene entry and an E.coli O157 gene entry
            s.get("hsa:10458+ece:Z5100")  
            # retrieves amino acid sequences of a human gene and an E.coli O157 gene
            s.get("hsa:10458+ece:Z5100/aaseq") 
            # retrieves the image file of a pathway map 
            s.get("hsa05130/image") 
            # same as above
            s.get("hsa05130", "image")


        Another example here below shows how to save the image of a given pathway::

           res =  s.get("hsa05130/image")
            # same as : res =  s.get("hsa05130","image")
            f = open("test.png", "w")
            f.write(res)
            f.close()

        .. note::  The input is limited up to 10 entries (KEGG restriction). 
        """
        _valid_options = ["aaseq", "ntseq", "mol", "kcf", "image", "kgml"]
        _valid_db_options = ["compound", "drug"]

        #self._checkDB(database, mode="find") 
        url = self.url + "/get/"+ dbentries

        if option:
            if option not in _valid_options:
                raise ValueError("invalid option. Must be in %s " % _valid_options)
            url +=  "/" + option

        res = self.request(url)

        return res


    def conv(self, target, source):
        """convert KEGG identifiers to/from outside identifiers 

        :param str target: the target database (e.g., a KEGG organism).
        :param str source: the source database (e.g., uniprot) or a valid
            dbentries; see below for details.

        :return: a dictionary with keys being the source and values being the target.

        Here are the rules to set the target and source parameters.

        If the second argument is not a **dbentries**, source and target
        parameters can be of two types:

            #. gene identifiers. If the target is a KEGG Id, then the source 
               must be one of *ncbi-gi*, *ncbi-geneid* or *uniprot*.

               .. note:: source and target can be swapped.
            #. chemical substance identifiers. If the target is one of the 
               following kegg database: drug, compound, glycan then the source 
               must be one of *pubchem* or *chebi*.

               .. note:: again, source and target can be swapped

        If the second argument is a **dbentries**, it can be again of two types:

            #. gene identifiers. The database used can be one ncbi-gi,
               ncbi-geneid, uniprot or any KEGG organism
            #. chemical substance identifiers. The database used can be one of
               drug, compound, glycan, pubchem or chebi only.

        .. note:: if the second argument is a dbentries, target and dbentries
            cannot be swapped.

        ::

            # conversion from NCBI GeneID to KEGG ID for E. coli genes
            conv("eco","ncbi-geneid")
            # inverse of the above example
            conv("eco","ncbi-geneid")
            #conversion from KEGG ID to NCBI GI 
            conv("ncbi-gi","hsa:10458+ece:Z5100")


        To make it clear by taking another example, you can either convert an
        entire database to another (e.g., from uniprot to Kegg Id all human gene
        IDs):: 

            uniprot_ids, kegg_ids = s.conv("hsa", "uniprot")

        or a subset by providing a valid **dbentries**::

            s.conv("hsa","up:Q9BV86+")


        .. warning:: dbentries are not check and are supposed to be correct. 
            See :meth:`check_idbentries` to help you checking a dbentries.

        .. versionchanged:: 1.1
            the output is now a dictionary, not a list of tuples
        """

        # The second argument may be a source_db or a dbentries so checking
        # second argument is kind of tricky because dbentries take lots of
        # different form.

        # for now, we only check the first argument.
        # gene identifiers
        isOrg = self.isOrganism(target)
        if isOrg==False and target not in ['ncbi-gi', 'ncbi-geneid', 'uniprot', 'pubchem',
                'chebi', 'drug', 'compound', 'glycan']:
                raise ValueError("""
    Invalid syntax. target must be a KEGG ID or
    one of the allowed database. See documentation og :meth:`conv` for
    details""")

        """if target in self.organismIds:
            if source not in ['ncbi-gi', 'ncbi-geneid', 'uniprot']:
                raise ValueError("Invalid source (must be ncbi-gi, ncbi-geneid or uniprot)")
        elif target in ['ncbi-gi', 'ncbi-geneid', 'uniprot']:
            if source not in self.organismIds:
                raise ValueError("Invalid source (must be a valid KEGG organism)")

        # for chenical substance identifiers
        elif target in ['drug', 'compound', 'glycan']:
            if source not in ['chebi', 'pubchem']:
                raise ValueError("Invalid source. Must be chebi or pubchem")
        elif target in ['chebi', 'pubchem']:
            if source not in ['drug', 'compound', 'glycan']:
                raise ValueError("Invalid source. Must be drug, compound or glycan")
        else:
            self.logging.info("arguments not checked")
        """


        url = self.url + "/conv/"+ target + '/' + source

        res = self.request(url)

        try:
            t = [x.split("\t")[0] for x in res.strip().split("\n")]
            s = [x.split("\t")[1] for x in res.strip().split("\n")]
            return dict([(x,y) for x,y in zip(t, s)])
        except:
            return res


    def link(self, target, source):
        """Find related entries by using database cross-references 

        :param str target: the target KEGG database or organism (see below for the list).
        :param str source: the source KEGG database or organism (see below for
            the list) or a valid dbentries involving one of the database; see
            below for details.

        The valid list of databases is pathway, brite, module, disease, drug, environ,
        ko, genome, compound, glycan, reaction, rpair, rclass, enzyme

        ::

            # KEGG pathways linked from each of the human genes
            s.link("pathway", "hsa") 
            # human genes linked from each of the KEGG pathways
            s.link("hsa", "pathway") 
            # KEGG pathways linked from a human gene and an E. coli O157 gene. 
            s.link("pathway", "hsa:10458+ece:Z5100")   
        """

        self._checkDB(target, mode="link") 

        url = self.url + "/link/"+ target + '/' + source
        res = self.request(url)
        return res

    def entry(self, dbentries):
        """Retrieve entry

        There is a weblink service (see http://www.genome.jp/kegg/rest/weblink.html)
        Since it is equivalent to :meth:`get`, we do not implement it for now 

        """
        raise NotImplementedError("Use :meth:`get` instead") 


    def show_pathway(self, pathId, scale=None, dcolor="pink", keggid={}):
        """Show a given pathway inside a web browser

        :param str pathId: a valid pathway Id. See :meth:`pathwayIds`
        :param int scale: you can scale the image with a value between 0 and 100
        :param str dcolor: set the default background color of nodes
        :param dict keggid: set color of entries contained in the pathway as 
            key/value pairs; can also be a list, in which case all nodes have 
            the same default color (red)

        .. note:: if scale is provided, dcolor and keggid are ignored.

        ::

            # show a pathway in the browser
            s.show_pathway("path:hsa05416", scale=50)

            # Same as above but also highlights some KEGG Ids (red for all)
            s.show_pathway("path:hsa05416", dcolor="white", 
                keggid=['1525', '1604', '2534'])

            # You can refine the colors using a dictionary:
            s.show_pathway("path:hsa05416", dcolor="white", 
                keggid={'1525':'yellow,red', '1604':'blue,green', '2534':"blue"})


        .. anotherway to higlight: http://www.kegg.jp/pathway/map00010+K00873+C00022
        """
        if pathId.startswith("path:"):
            pathId = pathId.split(":")[1]



        if scale:
            scale = int(scale/100.*100)/100. # just need 2 digits and a value in [0,1]
            url = "http://www.kegg.jp/kegg-bin/show_pathway?scale=" + str(scale)
            url += "&query=&map=" + pathId 
        else:
            url = "http://www.kegg.jp/kegg-bin/show_pathway?" + pathId
            if dcolor:
                url += "/default%%3d%s/" % dcolor
            if isinstance(keggid, dict):
                if len(keggid.keys())>0:
                    for k,v in keggid.iteritems():
                        if "," in v:
                            url += "/%s%%09%s/" % (k,v)
                        else:
                            url += "/%s%%09,%s/" % (k,v)
            elif isinstance(keggid, list):
                for k in keggid:
                    url += "/%s%%09,%s/" % (k,"red")

        self.logging.info(url)
        import webbrowser
        res = webbrowser.open(url)
        return res

    def show_module(self, modId):
        """Show a given module inside a web browser

        :param str modId: a valid module Id. See :meth:`moduleIds`

        Validity of modId is not checked but if wrong the URL will not open a 
        proper web page.
        """
        if modId.startswith("md:"):
            modId = modId.split(":")[1]
        url = "http://www.kegg.jp/module/" + modId
        self.logging.info(url)
        import webbrowser
        res = webbrowser.open(url)
        return res


    def check_dbentries(self, dbentries, checkAll=True):
        """Checks that all entries provided exist in the KEGG database

        :param str dbentries: a dbentries list. entries are separated by the +'
            sign (e.g., "hsa:10458+ece:Z5100")
        :param bool checkAll: checks all entries (Default) or stop as soon as an
            entry is not well formed.
        :return: True if all entries are correct. False otherwise

        ::

            s = Kegg()
            s.check_dbentries("hsa:10458+ece:Z5100")

        
        """
        import urllib2
        entries = dbentries.split("+")
        # we do not want logging here
        debugLevel = self.debugLevel
        self.debugLevel = "CRITICAL"
        allStatus = True
        for entry in entries:
            try:
                self.get(entry)
                status = True
            # if ill-formed, an entry will raise the 404 error.
            except urllib2.HTTPError, e:
                if e.code == 404:
                    status = False
                    allStatus = False
                    if checkAll == False:
                        print(entry, status)
                        return False
                else:
                    print(e)
                    raise
            except:
                self.debugLevel = debugLevel
                raise
            print(entry, status)
        # retrieve logging level
        self.debugLevel = debugLevel
        return allStatus

    # wrapper of all databases to ease access to them (buffered)

    def _get_db(self):
        return Kegg._valid_DB
    databases = property(_get_db, doc="Returns list of valid Kegg databases.")

    def _get_database(self, dbname, mode=0):
        res = self.list(dbname)
        assert mode in [0,1]
        return [x.split()[mode] for x in res.split("\n") if len(x)]

    def _get_organisms(self):
        if self._organisms == None:
            self._organisms = self._get_database("organism", 1)
        return self._organisms
    organismIds = property(_get_organisms, doc="Returns list of organism Ids")

    def _get_reactions(self):
        if self._reaction == None:
            self._reaction = self._get_database("reaction", 0)
        return self._reaction
    reactionIds = property(_get_reactions, doc="returns list of reaction Ids")

    def _get_enzyme(self):
        if self._enzyme == None:
            self._enzyme = self._get_database("enzyme", 0)
        return self._enzyme
    enzymeIds = property(_get_enzyme, 
        doc="returns list of enzyme Ids" + _docIds)

    def _get_organisms_tnumbers(self):
        if self._organisms_tnumbers == None:
            self._organisms_tnumbers = self._get_database("organism", 0)
        return self._organisms_tnumbers
    organismTnumbers = property(_get_organisms_tnumbers, 
        doc="returns list of organisms (T numbers)" + _docIds)

    def _get_glycans(self):
        if self._glycan == None:
            self._glycan = self._get_database("glycan", 0)
        return self._glycan
    glycanIds = property(_get_glycans, 
        doc="Returns list of glycan Ids" + _docIds)

    def _get_brite(self):
        if self._brite == None:
            self._brite = self._get_database("brite", 0)
        return self._brite
    briteIds = property(_get_brite, 
        doc="returns list of brite Ids." + _docIds)

    def _get_kos(self):
        if self._ko == None:
            self._ko = self._get_database("ko", 0)
        return self._ko
    koIds = property(_get_kos, doc="returns list of ko Ids" + _docIds)

    def _get_compound(self):
        if self._compound == None:
            self._compound =  self._get_database("compound", 0)
        return self._compound
    compoundIds = property(_get_compound, 
        doc="returns list of compound Ids" + _docIds)

    def _get_drug(self):
        if self._drug == None:
            self._drug =  self._get_database("drug", 0)
        return self._drug
    drugIds = property(_get_drug, doc="returns list of drug Ids" + _docIds)

    # set the default organism used by pathways retrieval
    def _get_organism(self):
        return self._organism
    def _set_organism(self, organism):
        if organism in self.organismIds:
            self._organism = organism
            self._pathway = None
            self._module = None
            self._ko = None
            self._glycan = None
            self._compound = None
            self._enzyme = None
            self._drug = None
            self._reaction = None
            self._brite = None
        else:
            self.logging.error("Invalid organism. Check the list in :attr:`organismIds` attribute")
            raise 
    organism = property(_get_organism, _set_organism, doc="returns the current default organism ")

    def _get_pathways(self):
        if self._organism == None:
            self.logging.warning("You must set the organism first (e.g., self.organism = 'hsa')")
            return

        if self._pathway == None:
            res = self.request(self.url + "/list/pathway/%s" % self.organism)
            orgs = [x.split()[0] for x in res.split("\n") if len(x)]
            self._pathway = orgs[:]
        return self._pathway
    pathwayIds = property(_get_pathways, doc="""returns list of pathway Ids for the default organism. 
        
    :attr:`organism` must be set.
    ::

        s = Kegg()
        s.organism = "hsa"
        s.pathwayIds

    """)

    def _get_modules(self):
        if self._organism == None:
            self.logging.warning("You must set the organism first (e.g., self.organism = 'hsa')")
            return

        if self._module == None:
            res = self.request(self.url + "/list/module/%s" % self.organism)
            orgs = [x.split()[0] for x in res.split("\n") if len(x)]
            self._module = orgs[:]
        return self._module
    moduleIds = property(_get_modules, doc="""returns list of module Ids for the default organism. 
        
    :attr:`organism` must be set.
    ::

        s = Kegg()
        s.organism = "hsa"
        s.moduleIds

    """)

    def lookfor_organism(self, query):
        """Look for a specific organism

        :param str query: your search term. upper and lower cases are ignored
        :return: a list of definition that matches the query
        """
        matches = []
        definitions = [" ".join(x.split()) for x in self.list("organism").split("\n")]
        for i, item in enumerate(definitions):
            if query.lower() in item.lower():
                matches.append(i)
        return [definitions[i] for i in matches]

    def lookfor_pathway(self, query):
        """Look for a specific pathway

        :param str query: your search term. upper and lower cases are ignored
        :return: a list of definition that matches the query
        """
        matches = []
        definitions = [" ".join(x.split()) for x in self.list("pathway").split("\n")]
        for i, item in enumerate(definitions):
            if query.lower() in item.lower():
                matches.append(i)
        return [definitions[i] for i in matches]

    def get_pathway_by_gene(self, gene, organism):
        """Search for pathways that contain a specific gene

        :param str gene: a valid gene Id
        :param str organism: a valid organism (e.g., hsa)
        :return: list of pathway Ids that contain the gene

        ::

            >>> s.get_pathway_by_gene("7535", "hsa")
            ['path:hsa04064', 'path:hsa04650', 'path:hsa04660', 'path:hsa05340']

        """ 
        p = KeggParser(verbose=False)
        p.organism = organism
        res = self.get(":".join([organism, gene]))
        dic = p.parse(res)
        if 'pathway' in dic.keys():
            return dic['pathway']
        else:
            print("No pathway found ?")


    def _old_get_pathway_by_gene(self, gene, organism):
        matches = []
        p = KeggParser()
        # using existing buffered list if same organism are used
        if self.organism == organism:
            Ids = self.pathwayIds
        else:
            p.organism = organism
            Ids = p.pathwayIds

        for i,Id in enumerate(Ids): 
            print("scanning %s (%s/%s)" % (Id, i, len(Ids)))
            if Id not in self._buffer.keys():
                res = p.get(Id)
                self._buffer[Id] = res
            else:
                res = self._buffer[Id]

            parsed = p.parse(res)
            if 'gene' in parsed.keys() and gene in parsed['gene']:
                print("Found %s in pathway Ids %s" % (gene, Id))
                matches.append(Id)
        return matches
 
    def parse_kgml_pathway(self, pathwayId, res=None):
        """Parse the pathway in KGML format and returns a dictionary (relations and entries)

        :param str pathwayId: a valid pathwayId e.g. hsa04660
        :param str res: if you already have the output of the query
            get(pathwayId), you can provide it, otherwise it is queried.
        :return: a tuple with the first item being a list of relations. Each
            relations is a dictionary with id2, id2, link, value, name. The
            second item is a dictionary that maps the Ids to 


        ::

            >>> res = s.parse_kgml_pathway("hsa04660")
            >>> set([x['name'] for x in res['relations']])
            >>> res['relations'][-1]
            {'entry1': u'15',
             'entry2': u'13',
             'link': u'PPrel',
             'name': u'phosphorylation',
             'value': u'+p'}

            >>> set([x['link'] for x in res['relations']])
            set([u'PPrel', u'PCrel'])

            >>> res['entries'][4]

        .. seealso:: `KEGG API <http://www.kegg.jp/kegg/xml/docs/>`_
        """
        output = {'relations':[], 'entries':[]}
        if res==None:
            res = self.easyXML(self.get(pathwayId, "kgml"))
        # here entry1 and 2 are Id related to the kgml file

        # read and parse the entries
        entries = [x for x in res.findAll("entry")]
        for entry in entries:

            output['entries'].append({
                'id': entry.get("id"),
                'name': entry.get("name"),
                'type': entry.get("type"),
                'link': entry.get("link"),
                'gene_names': entry.find("graphics").get("name")
                })

        relations = [(x.get("entry1"), x.get("entry2"), x.get("type")) for x in res.findAll("relation")]
        subtypes = [x.findAll("subtype") for x in res.findAll("relation")]

        assert len(subtypes) == len(relations)

        for relation, subtype in zip(relations, subtypes):
            if len(subtype)==0: # nothing to do with the species ??? TODO
                pass
            else:
                for this in subtype:
                    value = this.get("value")
                    name = this.get("name")
                    output['relations'].append({
                        'entry1':relation[0],
                        'entry2':relation[1],
                        'link':relation[2],
                       'value':value, 
                        'name':name})
        # we need to map back to KEgg IDs...
        return output

    def pathway2sif(self, pathwayId, uniprot=True):
        """Extract protein-protein interaction from KEGG pathway to a SIF format

        .. warning:: experimental Not tested on all pathway. should be move to
            another package such as cellnopt

        :param str pathwayId: a valid pathway Id
        :param bool uniprot: convert to uniprot Id or not (default is True)
        :return: a list of relations (A 1 B) for activation and (A -1 B) for
            inhibitions

        This is longish due to the conversion from KeggIds to UniProt.

        This method can be useful to provide prior knowledge network to software
        such as CellNOpt (see http://www.cellnopt.org)
        """
        res = self.parse_kgml_pathway(pathwayId)
        sif = []
        for rel in res['relations']:
            # types can be PPrel (protein-protein interaction only
            if rel['link'] != 'PPrel':
                continue
            if rel['name'] == 'activation':
                Id1 = rel['entry1']
                Id2 = rel['entry2']
                name1 = res['entries'][[x['id'] for x in res['entries']].index(Id1)]['name']
                name2 = res['entries'][[x['id'] for x in res['entries']].index(Id2)]['name']
                type1 = res['entries'][[x['id'] for x in res['entries']].index(Id1)]['type']
                type2 = res['entries'][[x['id'] for x in res['entries']].index(Id2)]['type']
                print("names:", rel, name1, name2)
                print(type1, type2)
                if type1!='gene' or type2!='gene':
                    continue
                if uniprot:
                    name1 = self.conv("uniprot", name1)[1][0]
                    name2 = self.conv("uniprot", name2)[1][0]
                print(name1, 1, name2)
                sif.append([name1, 1, name2])
            elif  rel['name'] == 'inhibition':
                Id1 = rel['entry1']
                Id2 = rel['entry2']
                name1 = res['entries'][[x['id'] for x in res['entries']].index(Id1)]['name']
                name2 = res['entries'][[x['id'] for x in res['entries']].index(Id2)]['name']
                type1 = res['entries'][[x['id'] for x in res['entries']].index(Id1)]['type']
                type2 = res['entries'][[x['id'] for x in res['entries']].index(Id2)]['type']
                print("names:", rel, name1, name2)
                print(type1, type2)
                if type1!='gene' or type2!='gene':
                    continue
                if uniprot:
                    name1 = self.conv("uniprot", name1)[1][0]
                    name2 = self.conv("uniprot", name2)[1][0]
                print(name1, -1, name2)
                sif.append([name1, -1, name2])
            else:
                print("#", rel['entry1'], rel['name'], rel['entry2'])

        return sif

    def __str__(self):
        txt = self.info()
        return txt


class KeggParser(Kegg):
    """This is an extension of the :class:`Kegg` class to ease parsing of dbentries

    This class provides a generic method :meth:`parse` that will read the output 
    of a dbentry returned by :meth:`Kegg.get` and converts it into a dictionary ready to use.

    The :meth:`parse` method is a dispatcher so you do not have to worry about the 
    type of entry you are using. It can be a pathway, a gene, a compound...
    ::

        from bioservices import *
        s = KeggParser()

        # Retrieve a KEGG entry
        res = s.get("hsa04150")

        # parse it
        d = s.parse(res)

    As a pedagogical example, you can then further process this dictionary. Here below, we convert
    the gene Ids found in the pathway into UniProt Ids::

        # Get the KEGG Ids in the pathway
        kegg_geneIds = [x for x in d['gene']]

        # Convert them
        db_up, db_kegg = s.conv("hsa", "uniprot")

        # Get the corresponding uniprot Ids
        indices = [db_kegg.index("hsa:%s" % x ) for x in kegg_geneIds]
        uniprot_geneIds = [db_up[x] for x in indices]

    However, you could also have done it simply as follows::

        kegg_geneIds = [x for x in d['gene']]
        uprot_geneIds = [s.parse(s.get("hsa:"+str(e)))['dblinks']["UniProt:"] for e in d['gene']]

    .. note:: The 2 outputs are slightly different. 

    .. seealso:: http://www.kegg.jp/kegg/rest/dbentry.html
    """
    def __init__(self, verbose=False):
        super(KeggParser, self).__init__(verbose=verbose)

    def parse(self, res):
        """A dispatcher to parse all outputs returned by :meth:`Kegg.get`

        :param str res: output of a :meth:`Kegg.get`.
        :return: a dictionary

        ::
        
            res = s.get("md:hsa_M00554")
            d = s.parse(res)
        """
        entry = res.split("\n")[0].split()[0]
        if entry == "ENTRY":
            dbentry = res.split("\n")[0].split(None, 2)[2]

        if "Pathway" in dbentry and "Module" not in dbentry:
            parser = self.parsePathway(res)
        elif dbentry.lower() == "pathway   module":
            parser = self.parseModule(res)
        elif "Drug" in dbentry: # can be Drug or "Mixture Drug"
            parser = self.parseDrug(res)
        elif "Disease" in dbentry: 
            parser = self.parseDisease(res)
        elif "Environ" in dbentry: 
            parser = self.parseEnviron(res)
        elif "Orthology" in dbentry: 
            parser = self.parseOrthology(res)
        elif "KO" in dbentry: 
            parser = self.parseOrthology(res)
        elif "Genome" in dbentry: 
            parser = self.parseGenome(res)
        elif "gene" in dbentry: 
            parser = self.parseGene(res)
        elif "CDS" in dbentry: 
            parser = self.parseGene(res)
        elif "Compound" in dbentry: 
            parser = self.parseCompound(res)
        elif "Glycan" in dbentry: 
            parser = self.parseGlycan(res)
        elif "Reaction" in dbentry: 
            parser = self.parseReaction(res)
        elif "RPair" in dbentry: 
            parser = self.parseRpair(res)
        elif "RClass" in dbentry: 
            parser = self.parseRclass(res)
        elif "Enzyme" in dbentry: 
            parser = self.parseEnzyme(res)
        else:
            raise NotImplementedError("Entry %s not yet implemented" % dbentry)
        return parser

    def parseDrug(self, res):
        """Parses a drug entry

        ::

            >>> res = s.get("dr:D00001")
            >>> d = s.parseDrug(res)
        """
        flatfile = ["ENTRY", "NAME", "PRODUCTS", "FORMULA", "EXACT_MASS",
            "MOL_WEIGHT", "COMPONENT", "SEQUENCE", "SOURCE", "ACTIVITY",
            "REMARK", "COMMENT", "TARGET", "METABOLISM", "INTERACTION",
           "PATHWAY","STR_MAP","BRITE","DBLINKS", "ATOM", "BOND", "BRACKET"]
        parser = self._parse(res, flatfile)
        return parser

    def parsePathway(self, res):
        """Parses a pathway entry

        ::

            >>> res = s.get("path:hsa10584")
            >>> d = s.parsePathway(res)

        """
        flatfile = ["ENTRY", "NAME", "DESCRIPTION", "CLASS", "PATHWAY_MAP", 
            "MODULE", "DISEASE", "DRUG", "DBLINKS", "ORGANISM", "ORTHOLOGY", 
            "GENE", "ENZYME", "REACTION", "COMPOUND", "REFERENCE", 
            "REL_PATHWAY", "KO_PATHWAY"]
        parser = self._parse(res, flatfile)
        return parser

    def parseModule(self, res):
        """Parses a module entry

        ::

            >>> res = s.get("md:hsa_M00554")
            >>> d = s.parseModule(res)
        """
        flatfile = ["ENTRY", "NAME", "DEFINITION",  "PATHWAY",
            "ORTHOLOGY", "CLASS", "BRITE", "ORGANISM", "GENE", "REACTION",
            "COMPOUND", "COMMENT", "DBLINKS", "REFERENCE", "REF_MODULE"]
        parser = self._parse(res, flatfile)
        return parser

    def parseDisease(self, res):
        """Parses a disease entry

        ::

            >>> res = s.get("ds:H00001")
            >>> d = s.parseDisease(res)
        """
        flatfile = ["ENTRY", "NAME", "DESCRIPTION", "CATEGORY", "PATHWAY", "GENE",
            "ENV_FACTOR", "MARKER", "DRUG", "COMMENT", "DBLINKS", "REFERENCE"]
        parser = self._parse(res, flatfile)
        return parser

    def parseEnviron(self, res):
        """Parses a environ entry
        ::

            >>> res = s.get("ev:E00001")
            >>> d = s.parseEnviron(res)
        """
        flatfile = ['ENTRY', "NAME", "CATEGORY", "COMPONENT", "SOURCE", 
            "REMARK", "COMMENT", "BRITE", "DBLINKS"]
        parser = self._parse(res, flatfile)
        return parser

    def parseOrthology(self, res):
        """Parses Orthology entry

        ::
        
            >>> res = s.get("ko:K00001")
            >>> d = s.parseOrthology(res)

        .. note:: in other case genes key is "gene". Here it is "genes".
        """
        flatfile = ['ENTRY', "NAME", "DEFINITION", "PATHWAY", "MODULE", 
            "DISEASE", "BRITE", "DBLINKS", "GENES", "REFERENCE"]
        parser = self._parse(res, flatfile)
        return parser

    def parseGenome(self, res):
        """Parses a Genome entry

        ::

            >>> res = s.get('genome:T00001')
            >>> d = s.parseGenome(res)

        """

        flatfile = ["ENTRY", "NAME", "DEFINITION", "ANNOTATION", "TAXONOMY", 
            "DATA_SOURCE", "ORIGINAL_DB", "KEYWORDS", "DISEASE", "COMMENT", 
            "CHROMOSOME", "PLASMID", "STATISTICS", "REFERENCE"]
        parser = self._parse(res, flatfile)
        return parser

    def parseGene(self, res):
        """Parses a gene entry

        ::

            >>> res = s.get("hsa:1525")
            >>> d = s.parseGene(res)

        """

        flatfile = ["ENTRY", "NAME", "DEFINITION", "ORTHOLOGY", "ORGANISM",
            "PATHWAY", "MODULE", "DISEASE", "DRUG_TARGET", "CLASS", "MOTIF", "DBLINKS", 
            "STRUCTURE", "POSITION", "AASEQ", "NTSEQ"]
        parser = self._parse(res, flatfile)
        return parser


    def parseCompound(self, res):
        """Parses a compound entry

        ::

            >>> s.get("cpd:C00001")
            >>> d = s.parseCompound(res)
        """
        flatfile = ["ENTRY", "NAME", "FORMULA", "EXACT_MASS", "MOL_WEIGHT",
            "SEQUENCE", "REMARK", "COMMENT", "REACTION", "PATHWAY", "ENZYME", "BRITE",
            "REFERENCE", "DBLINKS", "ATOM", "BOND", "BRACKET"]
        parser = self._parse(res, flatfile)
        return parser

    def parseGlycan(self, res):
        """Parses a glycan entry

        ::

            >>> res = s.get("gl:G00001")
            >>> d = s.parseGlycan(res)
        """
        flatfile = ["ENTRY", "NAME", "COMPOSITION", "MASS", "CLASS", "REMARK",
            "COMMENT", "REACTION", "PATHWAY", "ENZYME", "ORTHOLOGY", 
            "REFERENCE", "DBLINKS", "NODE", "EDGE", "BRACKET"]
        parser = self._parse(res, flatfile)
        return parser

    def parseReaction(self, res):
        """Parses a reaction entry

        ::

            >>> res = s.get("rn:R00001")
            >>> d = s.parseReaction(res)
        """
        flatfile = ["ENTRY", "NAME", "DEFINITION", "EQUATION", "REMARK",
            "COMMENT", "RPAIR", "ENZYME", "PATHWAY", "ORTHOLOGY", "REFERENCE"]
        parser = self._parse(res, flatfile)
        return parser

    def parseRpair(self, res):
        """Parses a rpair entry

        ::

            >>> res = s.get("rp:RP00001")
            >>> d = s.parseRpair(res)


        .. todo:: a better parsing
        """
        flatfile = ["ENTRY", "NAME", "COMPOUND", "TYPE", "RDM", "RCLASS",
            "RELATEDPAIR", "REACTION", "Enzyme  ENZYME", "ALIGN", "ENTRY1", "ENTRY2"]
        parser = self._parse(res, flatfile)
        return parser


    def parseRclass(self, res):
        """Parses a rclass entry

        ::

            >>> res = s.get("rc:RC00001")
            >>> d = s.parseRclass(res)

        .. todo:: a better parsing
        """
        flatfile = ["ENTRY", "DEFINITION", "RPAIR", "REACTION",
            "ENZYME","PATHWAY", "ORTHOLOGY"]
        parser = self._parse(res, flatfile)
        return parser

    def parseEnzyme(self, res):
        """Parses an enzyme entry

        ::

            >>> res = s.get('ec:1.1.1.1')
            >>> d = s.parseEnzyme(res)
        """
        flatfile = ["ENTRY", "NAME", "CLASS", "SYSNAME", "REACTION", "ALL_REAC",
            "SUBSTRATE", "PRODUCT", "COMMENT", "PATHWAY", "ORTHOLOGY", "GENES", 
            "REFERENCE"]
        parser = self._parse(res, flatfile)
        return parser

    def _parse(self, res, flatfile):
        """Reaction is currently a dictionary if more than one line maybe we
        want a list instead.


        """
        output = {}
        lines = res.split("\n")

        # scanning res and searching for flatfile keywords; keep track of
        # current one. Should be an entry first.
        current = None
        countref = 1 # reference counter for reference without a value
        for line in lines:
            line = line.strip()
            if line == "///" or len(line)==0:
                continue

            if len(line.split())>=2:# split each line to search for the first term
                key, text = line.split(None,1)
            else:
                key = line

            # REFERENCES are dealt with in the else
            if key in flatfile and \
                key not in ["REFERENCE", "AUTHORS", "TITLE", "JOURNAL"]:
                # store the first appearance of a key
                output[key.lower()] = line.split(key)[1].strip()
                current = key.lower()   # keep track of the current key
            else:
                # There are many instances of referneces that are followed
                # by authors/title/journal triplet
                if key == "REFERENCE":
                    current = key.lower()
                    if current not in output.keys():
                        # the first time, we create a dictionary
                        output[current] = {}

                # Sometimes, we want to create a dictionary. For instance, genes
                # but in other cases  we just want to append the text (e.g.
                # remarks)
                if current in ["gene", "reference", "rel_pathway", "orthology",\
                        "pathway","reaction", "compound", "dblinks", "marker"]:
                    if isinstance(output[current], dict) == False:
                        # The item may be of different length. In the gene case, we
                        # want to provide a list of dictionaries with key being the
                        # gene id but in some other cases, 
                        try:
                            key, value = output[current].split(" ",1)
                        except:
                            value = output[current]
                        output[current] = {key:value}
                    mode = "dict"
                else:
                    if isinstance(output[current], list) == False:
                        value = output[current]
                        output[current] = [value]
                    mode = "append"

                # For references only
                if current == "reference":
                    try:
                        key, value = line.strip().split(" ",1)
                    except:
                        key = line.strip()
                        countref += 1
                        value = str(countref)
                    if key.upper() in ["AUTHORS", "TITLE", "JOURNAL"]:
                        output[current][pubmed][key] = value
                    elif key.upper() == "REFERENCE":
                        pubmed = value.strip()
                        if pubmed in output[current].keys():
                            pubmed += "_" + str(countref)
                            countref += 1
                        output[current][pubmed] = {}
                else: # and all others
                    if mode == "dict":
                        try:
                            key, value = line.strip().split(" ",1)
                        except: # needed in INTERACTION case (DRUG)
                            key = line.strip()
                            value = ""
                        output[current][key.strip()] = value.strip()
                    else:
                        output[current].append(line)

        # some cleanup
        if "ntseq" in output.keys():
            data = output['ntseq']
            output['ntseq'] = {data[0]: reduce(lambda x,y:x+y, data[1:])}
        if "aaseq" in output.keys():
            data = output['aaseq']
            output['aaseq'] = {data[0]: reduce(lambda x,y:x+y, data[1:])}
        return output




