# -*- coding: utf8 -*-
'''
SAX HANDLER for requests of protocol
##############################################
#                 SAX HANDLER
#             for requests of protocol
# http://www.biocase.org/schemas/protocol/1.3
##############################################
$RCSfile: biocase_handler.py,v $
$Revision: 877 $
$Author: markus $
$Date: 2008-04-24 15:53:40 +0200 (Do, 24. Apr 2008) $

Sax handler to parse a request as defined by the BioCASE Unitlevel Access Protocol.
'''

from biocase.tools.saxbasehandler         import SaxBaseHandler
from biocase.wrapper.errorclasses     import *
from biocase.tools.various_functions         import isTrue
from biocase.wrapper.sql.operators     import BooleanValue, logicalOperator, comparisonOperator, compressOperator

copUnaryOperator    = logicalOperator.unaryCOP.keys()
copBinaryOperator   = logicalOperator.binaryCOP.keys()
copMultipleOperator = logicalOperator.multipleCOP.keys()
copOperator            = copUnaryOperator + copBinaryOperator + copMultipleOperator

lopUnaryOperator    = comparisonOperator.unaryLOP.keys()
lopBinaryOperator   = comparisonOperator.binaryLOP.keys()
lopOperator            = lopUnaryOperator + lopBinaryOperator

clopOperator        = lopOperator + copOperator


import logging
log = logging.getLogger("pywrapper.protocol.biocase")


# ----------------------------------------------------------------------------------
class requestHandler(SaxBaseHandler):
    '''parses the BioCASE protocol and fills a request obj with its data excl. the filter object if applicable.'''

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def __init__(self, requestObj):
        self.robj = requestObj
        self._isHeader = 0
        self._isSearch = 0
        self._isScan   = 0
        self._isFilter = 0
        self._value = ''
        SaxBaseHandler.__init__(self)
    
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def startElement(self, name, attrs):
        name, attrs = SaxBaseHandler.startElement(self, name, attrs)
        if name == 'header':
            self._isHeader = 1
        elif name == 'search':
            self._isSearch = 1
        elif name == 'scan':
            self._isScan   = 1
        if self._isSearch > 0:
            if name == 'responseFormat':
                # parse start record
                self.robj.start = attrs.get('start')
                try:
                    self.robj.start = int(self.robj.start)
                except:
                    log.warn("Record start is no positive integer! Reverted to default = 0", 1)
                    self.robj.start = 0
                # parse limit record
                self.robj.limit = attrs.get('limit')
                try:
                    self.robj.limit = int(self.robj.limit)
                    if self.robj.limit <= 0 and self.robj.limit <> -1973:
                        raise Exception()
                except:
                    log.warn("Record limit is no positive integer! Reverted to default = unlimited", 1)
                    self.robj.limit = 0

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def endElement(self, name):
        name=SaxBaseHandler.endElement(self, name)
        if name == 'header':
            self._isHeader = 0
        elif name == 'search':
            self._isSearch = 0
        elif name == 'scan':
            self._isScan   = 0
        if self._isHeader > 0:
            if name == 'type':
                self.robj.type = self._value
            elif name == 'source':
                self.robj.sources.append(self._value)
        if self._isSearch > 0:
            if name == 'requestFormat':
                self.robj.reqSchema = self._value.lower()
            elif name == 'responseFormat':
                self.robj.resSchema = self._value.lower()
            elif name == 'count':
                self.robj.count = isTrue(self._value)
        if self._isScan > 0:
            if name == 'requestFormat':
                self._value.lower()
                self.robj.reqSchema = self._value
            if name == 'concept':
                self.robj.scan = self._value



# ----------------------------------------------------------------------------------
class filterHandler(SaxBaseHandler):
    '''handler to parse the filter part of the BioCASE protocol.
    Returns a filter obj in self.filterObj.
    Needs a full CMF object to be passed to translate xpaths and
    integrate parental global CMF filters into the generated COPs.'''
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def __init__(self, CMFobj):
                # the CMF object for translating xpaths into mapping objects
        self.CMFobj = CMFobj
        self._args = [[]]
        self.filterObj = None
        self._tmp = None
        self._isFilter = 0
        self._isCOP = 0
        self._isVAL = 0
        self._isIN  = 0
        self._value = ''
        self._valueList = []
        SaxBaseHandler.__init__(self)

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def startElement(self, name, attrs):
        name, attrs = SaxBaseHandler.startElement(self, name, attrs)
        name = name.lower()
        if name == 'filter':
            self._isFilter = 1
        elif self._isFilter:
            # work with content
            if name in lopOperator:
                self._args.append([])
            elif name in copOperator:
                self._isCOP = 1
                self._path = attrs.get('path','')
                if name in copMultipleOperator:
                    self._isIN = 1
                    self._valueList = []
        if self._isIN and name == 'value':
            self._isVAL = 1

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def endElement(self, name):
        name=SaxBaseHandler.endElement(self, name)
        name=name.lower()
        if name == 'filter':
            self._isFilter = 0
        if self._isFilter:
            # work with content
            if self._isCOP:
                if name in copOperator:
                    # a comparison operator
                    self._isCOP = 0
                    # first get the CMF element object of the path
                    cmfEelemObj = self.CMFobj.path2ElemObj.get(self._path, None)
                    if cmfEelemObj is None:
                        # path does not exist in this CMF.
                        # evaluate this COP to false...
                        try:
                            raise RequestUnknownConceptError(self._path    )
                        except:
                            cop = BooleanValue(0)
                    else:
                        # check mappings
                        if not cmfEelemObj.isSearchable():
                            # searched path not searchable in providers DB.
                            # evaluate this COP to false...
                            try:
                                raise RequestNotSearchableConceptError(self._path    )
                            except:
                                cop = BooleanValue(0)
                        else:
                            # get list of searchable dbattribute objects
                            DbAttrObjects = cmfEelemObj.getSearchableDBAttrObjs()
                            # create first COP object. special cases:
                            if name in copMultipleOperator:
                                # in operator with value list !
                                self._value = self._valueList
                            elif name in copUnaryOperator:
                                # eg NULL operators with arg2=None
                                self._value = None
                            cop = comparisonOperator(name, DbAttrObjects[0], self._value)
                            if len(DbAttrObjects) > 1:
                                # several dbattributes are assigned to this xpath.
                                # create all comparison operators and combine them with OR:
                                for DBattrObj in DbAttrObjects[1:]:
                                    cop2 = comparisonOperator(name, DBattrObj, self._value)
                                    cop = logicalOperator('or', [cop, cop2])
                            # integrate all parental global CMF filters of this CMF element into the COP/LOP
                            if cop is not None:
                                # check existing global filters of parental records
                                globFilterObj = logicalOperator('and', cmfEelemObj.getParentalFilterList())
                                globFilterObj = compressOperator(globFilterObj)
                                if globFilterObj is not None:
                                    log.debug("Global CMF Filters for concept %s integrated into the COP: %s"%(unicode(self._path), str(globFilterObj)) )
                                    # include original COP
                                    if globFilterObj.__class__ == logicalOperator:
                                        # its already a logical operator
                                        globFilterObj.args.append(cop)
                                        cop = globFilterObj
                                    else:
                                        # this is only a comparison operator. make it a logical AND
                                        cop = logicalOperator('and', [globFilterObj, cop])
                    # append args
                    self._args[len(self._args)-1].append(cop)
                elif self._isVAL:
                    self._valueList.append(self._value)
            else:
                # create logical operator with child arguments
                lop = logicalOperator(name, self._args[len(self._args)-1])
                # append to args
                del self._args[len(self._args)-1]
                self._args[len(self._args)-1].append(lop)
        if name == 'in':
            self._isIN = 0
            self._value = None
        if name == 'value':
            self._isVAL = 0



    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def endDocument(self):
        if self._tmp <> None:
            self.filterObj = self._tmp
        else:
            try:
                self.filterObj = self._args[0][0]
            except:
                self.filterObj = None

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def characters(self, ch):
        if self._isCOP:
            self._value += ch
            try:
                data = getData(icode(self._value))
                if data <> None:
                    self._tmp = getTmp( [ocode(x) for x in data] )
            except:
                pass




# ----------------------------------------------------------------------------------
if __name__ ==  "__main__":
    pass



