# -*- coding: utf8 -*-
'''
CMF BASE CLASS
*************************
***  CMF BASE CLASS
*************************
$RCSfile: cmf_base.py,v $
$Revision: 926 $
$Author: markus $
$Date: 2008-05-07 14:26:47 +0200 (Mi, 07. Mai 2008) $

Classes defining how to produce a filled CMF object and how to create a ElementTree object from this.
'''



import os, xml.sax
from string                             import join, find
from types                              import *
from datetime                           import datetime
import xml.etree.ElementTree as ET
from biocase.tools.xmlutils             import element, subelement, prettyprint, addOptionalAttributes, cleanupNamespaces
from biocase.tools.caching              import getModifiedDatetime
from biocase.wrapper.errorclasses       import *
from biocase.wrapper.cmf_element        import CMFAttributeClass, CMFElementClass, NS21
from biocase.wrapper.cmf_handler        import CMFhandler_2_1
from biocase.tools.various_functions    import unique
from biocase.tools.caching              import getPickleFilename
from biocase.wrapper.graph.tablegraph   import tableTreeClass

import biocase.configuration
cfg = biocase.configuration.Cfg()

import logging
log=logging.getLogger("pywrapper.cmf")


# ------------------------------------------------------------------------------------
class CMFClass:
    '''The base class for a CMF object. 
    Contains static information and the CMF element objects.'''
    __revision__ = "$Revision: 926 $"
    
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def __init__(self):
        # set when loading data
        self.absFilename       = None
        #
        # properties set later by the method "loadCMFdata"
        #
        # metadata
        self.name              = None
        self.acronym           = None
        self.version           = None
        self.generated_by      = 'PyWrapper'
        self.generated_when    = str(datetime.now())
        self.modified_when     = str(datetime.now())
        # other incl settings
        self.omitNS            = None # dont output any namespace declarations
        self.recIdents           = []      # list of recordIdentifiers as xpaths ???
        self.rootCMFElementObj = None # the root element of the CMF dataElements
        self.tableTreeObj      = tableTreeClass()  # cmf specific rooted tree created from the PSF tablegraph
        # alternative root table aliases. Used by SPICE/Species2000 protocol only!
        self.altRootTableAliasSPICE = {} # key= request type number (1,2,3,4,5), value=root table alias name
        #
        # properties not defined in the CMF itself
        #
        # direct access to an CMF element object through its path:
        self.path2ElemObj    = {}    # dictionary key=xpath, value=CMF Element Object (CEO)
        # convenient dictionary listing only the xpaths that have mappings! 
        self.path2mappings    = {}    # dictionary key=xpath, value=list of mapping objects
        # all DB attributes found in CMF as a list of Mapping Objects
        self.allDBAttr  = []
        
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def __repr__(self):
        return "CMF Base Element: Name:%s, Acronym:%s, Version:%s, Rec Identifier=%s, static tables=%s, tree=%s, allDBAttr=%s,\nROOT ELEMENT:%s"%(str(self.name), str(self.acronym), str(self.version), str(self.recIdents), str(self.tableTreeObj.staticTables.keys()), str(self.tableTreeObj), str(self.allDBAttr), str(self.rootCMFElementObj))

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def fullRepr(self):
        return self.__repr__() + "\n" + self.rootCMFElementObj.fullRepr()

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def __pickle__(self, filename):
        '''pickles all content of an CMF object instance into a file 
        using a hash for storing all important properties.'''
        import cPickle as pickle
        # create hash to pickle
        pickleHash = {}
        pickleHash['revision']        = self.__class__.__revision__
        pickleHash['encoding']        = CMFAttributeClass.encoding
        pickleHash['absFilename']     = self.absFilename    
        pickleHash['rec_definitions'] = self.recIdents
        pickleHash['name']            = self.name
        pickleHash['acronym']         = self.acronym
        pickleHash['version']         = self.version
        pickleHash['generated_when']  = self.generated_when
        pickleHash['generated_by']    = self.generated_by
        pickleHash['modified_when']   = self.modified_when
        pickleHash['omitNS']          = self.omitNS
        pickleHash['tabletree']       = self.tableTreeObj
        pickleHash['root_cmf_object'] = self.rootCMFElementObj
        pickleHash['path2elem_obj']   = self.path2ElemObj
        pickleHash['path2mappings']   = self.path2mappings
        pickleHash['all_db_attr']     = self.allDBAttr
        pickleHash['altRootTableAliasSPICE'] = self.altRootTableAliasSPICE
        
        # now pickle this hash...
        try:
            pickleFile = file(filename,'w')
            pickle.dump(pickleHash, pickleFile)
            pickleFile.close()
            log.debug("Pickled CMF object '%s'"%(filename))
        except:
            log.warn("Could not pickle the CMF object '%s'"%(filename))
            raise CouldNotPickleError()
            
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def __unpickle__(self, filename):
        '''restores a CMF object from a pickled file
        using a hash for storing all important properties.'''
        import cPickle as pickle
        log.debug("Load the pickled CMF object '%s'"%(os.path.split(filename)[1]))
        CMFfo = file(filename, 'r')
        pickleHash = pickle.load(CMFfo)
        CMFfo.close()
        # check revision number of pickled object and this class.
        # Return true if they are the same, otherwise false.
        if pickleHash['revision'] <> self.__class__.__revision__:
            log.debug("Pickled file is of different revision. Dont load.")
            return False
        else:
            log.debug("Pickled file is of latest revision.")
            # update this instance with the loaded hash data...
            CMFAttributeClass.encoding = pickleHash['encoding']
            self.absFilename       = pickleHash['absFilename']
            self.recIdents         = pickleHash['rec_definitions']
            self.name              = pickleHash['name']
            self.acronym           = pickleHash['acronym']
            self.version           = pickleHash['version']
            self.generated_when    = pickleHash['generated_when']
            self.generated_by      = pickleHash['generated_by']
            self.modified_when     = pickleHash['modified_when']
            self.omitNS            = pickleHash['omitNS']
            self.tableTreeObj      = pickleHash['tabletree']
            self.rootCMFElementObj = pickleHash['root_cmf_object']
            self.path2ElemObj      = pickleHash['path2elem_obj']  
            self.path2mappings     = pickleHash['path2mappings']
            self.allDBAttr         = pickleHash['all_db_attr']     
            self.altRootTableAliasSPICE = pickleHash['altRootTableAliasSPICE']
            # loading succeeded
            return True
        
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def __readCMF__(self, filename, psfObj=None, CMFElementClassSubstitute=CMFElementClass):
        '''Loads this CMF object from a raw CMF xml-file.
        It parses this data    without knowing anything more than the CMF itself.
        Additional derived data is
        - path2ElemObj dict
        - path2mappings dict
        If a psf object was supplied the cmf object is initiated for output
        and a new tabletree object is generated for this cmf object.'''
        # remember original file and read file modification date
        self.absFilename   = filename
        self.modified_when = getModifiedDatetime(filename)
        log.info("CMF last modified: %s"%str(self.modified_when))
        # remove existing secondary "cache" data of this object:
        log.debug("Reset CMF convenience cache data hash.")
        self.path2ElemObj    = {}    # dictionary key=xpath, value=CMF Element Object (CEO)
        self.path2mappings    = {}    # dictionary key=xpath, value=list of mapping objects
        self.allDBAttr  = []
        # first check for CMF version being used in the file
        log.debug("Loading CMF from the source XML file.")
        ch = CMFhandler_2_1(self)
        xml.sax.parse(filename, ch)
        self.rootCMFElementObj  = CMFElementClassSubstitute(self, self.path2ElemObj, ch.initDict    )
        # fill path2mappings dict
        for cmfObj in self.path2ElemObj.values():
            if len(cmfObj.mappings) > 0:
                self.path2mappings[cmfObj.xpath] = cmfObj.mappings
        # init the cmf object and generate a tabletree if a psf object and a root tablealias was supplied
        if psfObj is not None and ch.rootTable is not None and ch.rootTable <> '':
            log.info("Init CMF with root table: '%s'"%unicode(ch.rootTable))
            self.initForDataProcessing(psfObj, ch.rootTable, ch.staticTables)
        else:
            log.info("Dont init CMF. No root table or PSF supplied")
            # at least set the information about the root table and static tables taken from the CMF to not lose data!
            self.tableTreeObj = tableTreeClass()
            self.tableTreeObj.rootTableAlias = ch.rootTable
            for STA in ch.staticTables:
                self.tableTreeObj.staticTables[STA] = 1

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def loadCMFdata(self, filename, psfObj=None, pickle=None):
        '''Loads this CMF object from a file.
        If pickling is enabled it first tries to find a pickled version of the CMF.
        If this is outdated it parses the real xml file and creates a new pickle for it.
        If the optional psf object is passed, the CMF object is initiated for output
        and it is checked that the pickled cmf object is newer than the PSF file.
        The method returns True if there are CMFelements existing, otherwise False.
        '''
        import os
        # check if file exists
        log.info("Load CMFile '%s'" % filename)
        if type(filename) not in(StringType, UnicodeType) or not os.path.isfile(filename):
            raise "CMFile %s does not exist. Cant load that file" % filename
        # define the pickled version of the file within the cache dir
        CMFpick = getPickleFilename(filename)
        # does a pickled file exist and was it modified later than the CMF and the PSF (if supplied) ? 
        if cfg.server.pickle and os.path.isfile(CMFpick) and getModifiedDatetime(CMFpick) > getModifiedDatetime(filename) and (psfObj is None or getModifiedDatetime(CMFpick) > getModifiedDatetime(psfObj.absPSFilename)):
            isGood = self.__unpickle__(CMFpick)
            # does the unpickled object match with this version of the class?
            if not isGood:
                # no, reload from xml file
                self.__readCMF__(filename, psfObj=psfObj)
                if cfg.server.pickle and psfObj is not None:
                    # pickle this new object
                    self.__pickle__(CMFpick)
        else:
            # CMF is newer than pickled version. Create a new object:
            self.__readCMF__(filename, psfObj=psfObj)
            if cfg.server.pickle and psfObj is not None:
                # pickle this new object
                self.__pickle__(CMFpick)
        if self.rootCMFElementObj is None:
            return False
        # report namespace omitting state
        if self.omitNS:
            log.debug("Omitting namespaces in output.")    
        return True
        
        
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def initForDataProcessing(self, psfObj, rootTableAlias, staticTableAliasList=[]):
        '''Calculates needed information from a CMF instance to be ready to process data.
        It creates the following internal datastructures with the help of the psfObj:
        - create RepNodes with locked tables (for XML output)
        It creates the following internal datastructures with the root and static table aliases:
        - tabletree object
        '''
        # make static table alias list unique
        staticTableAliasList = unique(staticTableAliasList)
        # generate a tabletree object from the PSF graph with the given root tablealias
        self.tableTreeObj = psfObj.getTableTreeObj(rootTableAlias, staticTableAliasList)
        #
        # update the CMF element objects
        #
        # set encoding for all CMFElement/Attribute objects:
        CMFAttributeClass.encoding = psfObj.encoding
        # init the CMF nodes:
        self.initNodes(psfObj)
        
    def initNodes(self, psfObj):
        '''Recalculate the active nodes, the allDBAttributes list and the mapping indexes of all db attributes.'''
        log.debug("Init CMF nodes (Resetting PK.rsindex, allDBattribute, RepNodePKDBAttrObjLoL)")
        # recalc all db attributes and their recordset index
        # reset all primary key db attributes
        for dbattr in psfObj.getCompletePKDBAttrObjList():
            dbattr.rsindex = None
        # recalc the * list of all db attributes
        self.allDBAttr = self.getAllDBAttributes()
        for i in range( len(self.allDBAttr) ):
            self.allDBAttr[i].rsindex = i+1 # +1 because every query appends the root table PK first!
        # remove existing locked tables/primary key db attributes from locked nodes
        for node in self.walk():
            node.RepNodePKDBAttrObjLoL=[]
        # activate rep nodes and include primary key db attributes in * list
        self.rootCMFElementObj.activateRepNodes(self, psfObj)

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def getMappedConcepts(self):
        '''Return list of cmf node objects that have mappings set.'''
        cmfObjs = []
        for path in self.path2mappings.keys():
            cmfObjs.append(self.path2ElemObj[path])
        return cmfObjs        
        
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def getAllDBAttributes(self):
        '''Return distinct list of all db attribute mappings used in any node object.'''
        DBAttributes = []
        for node in self.walk():
            #log.debug("DBAttributes from node %s: %s" % (node.Name, unicode(node.getDBAttributes())))
            DBAttributes += node.getDBAttributes()
        return unique(DBAttributes)
        
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def removeSubtree(self, path, psfObj):
        '''Removes a node and all its children from the CMF tree.
        Method takes care of adjusting derived properties.'''
        log.debug("Remove subtree '%s' of CMF." % unicode(path))
        # get node to be removed
        node = self.getConceptByPath(path)
        if node is None:
            log.warn("No subtree '%s' found. Ignored" % path)
        else:
            for n in node.walk():
                if self.path2ElemObj.has_key(n.xpath):
                    del self.path2ElemObj[n.xpath]
                else:
                    log.warn("Can't find node %s in path2ElemObj dict." % n.xpath)
                if self.path2mappings.has_key(n.xpath):
                    del self.path2mappings[n.xpath]
            # remove node itself
            node.detachFromParent()
            if self.path2ElemObj.has_key(path):
                del self.path2ElemObj[path]
            if self.path2mappings.has_key(path):
                del self.path2mappings[path]
            # recalculate the AllDBAttributes list and repeatability of remaining nodes
            self.initNodes(psfObj)
            log.debug("Current allDBAttr: %s" % unicode(self.allDBAttr))
        
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def removeMappings(self):
        '''Removes all mappings from the object and returns them as a hash:
        key=xpath, value=list of mapping objects'''
        mappings = {}
        # remove all mappings
        for path in self.path2mappings.keys():
            cmfObj = self.path2ElemObj[path]
            mappings[path]=cmfObj.mappings
            cmfObj.mappings = []
        return mappings
                
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def setMappings(self, mappingHash={}):
        '''Sets mappings of this CMF object and returns unmatched xpaths as a list.'''
        nonExistingPath = {}
        for path, mappings in mappingHash.items():
            if self.path2ElemObj.has_key(path):
                self.path2ElemObj[path].mappings += mappings
            else:
                nonExistingPath[path] = 1
        return nonExistingPath.keys()
                
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def removeDontRepeats(self):
        '''Removes all dont repeat settings from the object and returns them a list of xpaths.'''
        dontreps = []
        # remove all dont repeats
        for path, elemObj in self.path2ElemObj.items():
            if not elemObj.isAttribute() and elemObj.RepeatDont:
                dontreps.append(path)
                elemObj.RepeatDont = None
        return dontreps

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def setDontRepeats(self, dontreps = []):
        '''Sets dont repeat flags to true for a list of xpaths.
        Returns the list of xpaths which are not used by this CMF object.'''
        nonExistingPath = {}
        for path in dontreps:
            if self.path2ElemObj.has_key(path):
                self.path2ElemObj[path].RepeatDont = 1
            else:
                nonExistingPath[path]=1
            if elemObj.RepeatDont:
                dontreps.append(path)
        return nonExistingPath.keys()
                
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def removeFilters(self):
        '''Removes all filters of the cmf object and returns them in a hash.'''
        # remove all filter objects
        filters = {}
        for path in self.path2ElemObj.keys():
            cmfObj = self.path2ElemObj[path]
            if cmfObj.filterObj is not None:
                filters[path] = cmfObj.filterObj
                cmfObj.filterObj = None
        return filters

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def setFilters(self, filterHash = {}):
        '''Sets filters of this cmf object and returns unmatched xpaths in a list.'''
        nonExistingPath = {}
        for path, filterObj in filterHash.items():
            if self.path2ElemObj.has_key(path):
                self.path2ElemObj[path].filterObj = filterObj
            else:
                nonExistingPath[path] = 1
        return nonExistingPath.keys()

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def countConcepts(self):
        '''Count the total number of leaf concepts with w3ctype.'''
        return len( self.listConcepts(inclAttributes=1, onlyLeafs=1, w3cType=True) )

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def countMappedConcepts(self):
        '''Count the number of concepts that have mappings.'''
        return len(self.path2mappings)

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def countGlobalFilters(self):
        '''Count the number of global filters.'''
        return len( self.listGlobalFilters() )

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def listConcepts(self, inclAttributes=1, onlyLeafs=0, w3cType=True, returnObjects=False):
        '''List all xpaths that are defined in this cmf and can be reached through the walking method of the elements.
        Optionally exclude attributes
        Optionally only list leaf nodes
        Optionally filter by their w3c type:  w3cType=
          True -> only concepts with a w3c type
          None -> any concepts
          False -> only concepts without a w3c type
        '''
        if w3cType:
            # nodes with w3ctype
            boolFunc=lambda obj: hasType(obj)
        elif w3cType is None:
            # any node
            boolFunc=lambda obj:1
        else:
            # nodes without w3ctype
            boolFunc=lambda obj: not hasType(obj)
        nodes = self.rootCMFElementObj.walk(boolFunc=boolFunc, inclAttributes=inclAttributes, onlyLeafs=onlyLeafs)
        if returnObjects:
            return nodes
        else:
            return [obj.xpath for obj in nodes]

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def listDontRepeats(self):
        '''List all xpaths that are have dont repeat set to true.'''
        return [obj.xpath for obj in self.rootCMFElementObj.walk(hasDontRep, inclAttributes=0)]

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def listGlobalFilters(self):
        '''List all xpaths that have global filters set.'''
        return [obj.xpath for obj in self.rootCMFElementObj.walk(hasGlobalFilter)]

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def listMappingFilters(self):
        '''List all xpaths that have mapping filters set.'''
        return [obj.xpath for obj in self.rootCMFElementObj.walk(hasMapFilter)]

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def getConceptByPath(self, path):
        '''Returns the cmf node object for a path or None.'''
        try:
            return self.path2ElemObj[path]
        except:
            return None
    
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def getMappingsByConcept(self, path):
        '''Returns list of all mappings of a given concept path (CMF element).'''
        return self.path2ElemObj[path].getMappings()
    
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def addMappingToConcept(self, path, mapObj):
        '''Adds a new mapping object to the mapping list of a given concept path (CMF element).'''
        self.path2ElemObj[path].addMapping(mapObj)
    
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def delMappingOfConcept(self, path, index):
        '''Removes a mapping object to the mapping list of a given concept path (CMF element).'''
        self.path2ElemObj[path].delMapping(index)
        
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def getRootNamespace(self):
        '''Returns the namespace of the root element node.'''
        if self.rootCMFElementObj is not None:
            NS = self.rootCMFElementObj.getNamespace()
        else:
            NS = None
        return NS
        
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def getRootTableAlias(self):
        '''Returns the name of the root tablealias of the tablegraph object.'''
        if self.rootCMFElementObj is not None and isinstance(self.tableTreeObj, tableTreeClass):
            alias = self.tableTreeObj.rootTableAlias
        else:
            alias = None
        return alias
        
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def setRootTableAlias(self, alias):
        '''Sets the name of the root tablealias of the tablegraph object. 
        Does NOT automatically calculate a table tree from it.'''
        self.tableTreeObj.rootTableAlias = alias
        
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def getStaticTableAliases(self):
        '''Returns the list of static tablealiases of the object.'''
        return self.tableTreeObj.staticTables.keys()
        
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def setStaticTableAliases(self, aliasList):
        '''Sets the list of static tablealiases of the tablegraph object.
        ATTENTION: THIS DOES NOT RECALCULATE THE TREE OBJECT !'''
        self.tableTreeObj.staticTables = {}
        for alias in aliasList:
            if alias is not None:
                self.tableTreeObj.staticTables[alias] = None
        
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def setXMLTypeListInMappingObjs(self):
        '''Adds an additional attribute to each Mapping object in self.allDBAttr:
        mapObj.XMLtypes is a list of all existing XMLTypes this mapping object was used for in the CMF.'''
        for CMFobj in self.path2ElemObj.values():
            for mapObj in CMFobj.mappings:
                try:
                    mapObj.XMLtypes.append(CMFobj.Type)
                except:
                    mapObj.XMLtypes = []
                    mapObj.XMLtypes.append(CMFobj.Type)
        for dbao in self.allDBAttr:
            try:
                dbao.XMLtypes = unique(dbao.XMLtypes)
            except:
                pass
    
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def needsTotalMatchedRecords(self):
        # is there any TotalMatchedRecords directive present in this CMF?
        hasTotalMatchedRecords = False
        for xpath, mapObjList in self.path2mappings.items():
            for mapObj in mapObjList:
                if mapObj.hasTotalMatchedRecords():
                    return True
        return False

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def createXMLResultET(self, Recordset):
        '''Create a XML dom object as the result for a given recordset.
        The optional filterObj being used to query for data is passed here
        to process the count TotalMatchedRecords directive.'''
        #
        # TO RECORD SET PROCESSING
        # pass whole recordset to root element
        #
        log.debug("start of XML creation from recordset")
        try:
            rootETList = self.rootCMFElementObj.createETListFromRecordSet(Recordset)
            if len(rootETList) == 0:
                root = None
            elif len(rootETList) == 1:
                root = rootETList[0]
            elif len(rootETList) > 1:
                log.warn("There were several root elements in the final XML content document. The first one was taken - please check the CMF configuration!")
                root = rootETList[0]
        except IOError:
            root = None
            log.error("unspecified error when creating XML elements")
        log.debug("end of XML creation from recordset")
        return root
    
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def walk(self, boolFunc=lambda x:1, inclAttributes=1, onlyLeafs=0):
        '''Shortcut to the element walk method of the root element.'''
        return self.rootCMFElementObj.walk(boolFunc, inclAttributes, onlyLeafs)

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def writeCMF(self, filename=None):
        '''Writes the CMF object to an xml file.'''
        if filename is None:
            filename = self.absFilename
        cmfET = self.createCMFET()
        CMFile = file(filename,'w')
        cleanupNamespaces(cmfET)
        prettyprint(cmfET, CMFile)
        CMFile.close()

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def createCMFET(self):
        '''Returns an ElementTree object that represents this object as a ConMapFile of version 2.1'''
        return self.createCMFET_2_1()
    
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def createCMFET_2_1(self):
        '''Returns an ElementTree object that represents this object as a ConMapFile of version 2.1'''
        # root node
        conmap_node = element(NS21, "conceptMapping")
        # first add help comment
        comment = 'please see http://ww3.bgbm.org/bps2/Configuration#Mapping for help on configuration options'
        conmap_node.append(ET.Comment(comment))
        # metadata
        meta_node = subelement(conmap_node, NS21, "metadata")
        subelement(meta_node, NS21, "generated", when=self.generated_when, by=self.generated_by)
        subelement(meta_node, NS21, "name", self.name, acronym=self.acronym, version=self.version)
        # settings
        settings_node = subelement(conmap_node, NS21, "settings")
        subelement(settings_node, NS21, "rootTableAlias", self.tableTreeObj.rootTableAlias)
        # SPICE
        spice_node = subelement(settings_node, NS21, "SPICE")
        for rtype, alias in self.altRootTableAliasSPICE.items():
            subelement(spice_node, NS21, "altRootTableAlias", alias, reqType=str(rtype))
        # static tables
        for t in self.tableTreeObj.staticTables.keys():
            subelement(settings_node, NS21, "staticTableAlias", t)
        # record identifiers
        for r in self.recIdents:
            subelement(conmap_node, NS21, "recordIdentifier", r)
        # CMF elements
        dataElementNode = self.rootCMFElementObj.createCMFET_2_1()
        if dataElementNode <> None:
            conmap_node.append(dataElementNode)
        return conmap_node



# ------------------------------------------------------------------------------------
# BOOLEAN FUNCTIONS TO BE USED AS boolFunc WITH THE walk METHOD OF THE CMFClass
# ------------------------------------------------------------------------------------
def hasType(obj):
    '''Checks if a node has a w3c type set.'''
    if obj.Type is not None:
        return 1
    else:
        return 0

# ------------------------------------------------------------------------------------
def hasDontRep(obj):
    '''Checks if a node has the dont repeat flag set.'''
    if obj.RepeatDont:
        return 1
    else:
        return 0

# ------------------------------------------------------------------------------------
def hasGlobalFilter(obj):
    '''Checks if a node has a global filter object set.'''
    if obj.filterObj is None:
        return 0
    else:
        return 1

# ------------------------------------------------------------------------------------
def hasMapFilter(obj):
    '''Checks if a node has a mapping filter set.'''
    for mapObj in obj.mappings:
        if mapObj.filterObj is not None:
            return 1
    return 0

