Personal tools
You are here: Home Developers Corner Run time behaviour XmlIo.py
Document Actions

XmlIo.py

Python XmlIo functionality. The header comment is the most authoritative reference for Python Xml I/O

Click here to get the file

Size 36.1 kB - File type text/python-source

File contents

""" Python XML I/O

Data XML I/O code.

Works off XML I/O maps generated and written in memops/scripts_v2/xmlio.
See there for tag names and I/O map organisation.
For every abstract type, attribute and link there is a map dictionary that 
describes how the object should be handled. These dictionaries can be 
accessed either from the XML tag (therough loadMaps) or from the attribute 
or type name. The tag to write and the type to create or attribute to set
are kept inside the map; for version compatibility purposes the XML tag and
attribute/type name may reflect different versions.

Uses ElementTree for parsing file when reading, but writes the files directly.
The only characters escaped are '<', '>', '&', and, in attributes, '"'.

Object are created as empty shells(invalid) and attributes are set after
the fact. A number of implementation attributes (e.g. parent-child links, 
TopObjects bookkeeping) have to be set explicitly after the fact. Intra-file 
links are dereferenced after the file has been read.


There is an element or attribute for every class object, and DataObjType object.
These elements have attributes and contain  other elements  but no text. 
Out-of-package links have their own elements, that have no attributes but
contain other elements that make uop, in order, the full key for the linked-to 
object. Note that the exolink tags determine the class of the element linked to,
so that a given out-of-package link may sontain objects of different classes
with different keys.
Simple data type are mostly incorporated in their containing attributes, 
but may be handled as elements with no attributes containing text only.

Attributes and links may be stored in three ways:

A) If hicard == 1 they may be put into attributes of the element that defines
the containing object. For text types this is used only for types Word and
Token, for other simple types and for nitra-package links this is used whenever
possible. If the file is written with compact=False (default is compact=True)
these data are insted written according to B.

B) Intra-package links and simple type attributes that can not contain white
space are  written as an XML element per attribute containing a (white-space
separated) XML list of values. This is used when hicard != 1 (if compact=True)
or in all cases (if compact=False)

C) Attributes that may contain whitespace (most text types and DataObjTypes),
out-of-package links, and child links are written as elements without 
attributes that contain other elements. Individual out-of-package links (which 
are semantically different) are writetn the same way. The contents may be
class elements (for child links), DataObjType elements, SImple type elements, 
or out-of-package link elements.



Every Class element has an attribute '_ID' that gives an arbitrary object ID of
the form '_int'. Intra-package links use these IDs as object pointers.  The IDs
depend on the order in which objects are encountered while saving; it follows
that different data files cannot be compared with 'diff', because otherwise
identical dat may be using different IDs.



By default all two-way intra-package links are stored at both ends, and
attributes are stored also if they are identical to the default value. 
Alternatively data can be written with the switch 'simplified=True'. In this
case attribtues are omitted if they are identical to the default value, and
one-to-many links are written only on the 'many' side (i.e. as a single list 
of IDs appearing in a single object). There is no provision for storing 
many-to-many links at one end only - this would be possible for unordered
many-to-many links, although not for ordered ones.

Inter-package links are stored on one side only - the side of the importing 
package


Attributes or links that are unset, i.e. have the value None, or are empty,
are not stored.


The code has been partially prepared for storing and loading subtrees, i.e. 
trees that are not headed by a TopObject. Some more changes would still be 
needed, though. In order todo this you would need special I/O maps, so that
out-of-tree links were written as out-of-package links.


======================COPYRIGHT/LICENSE START==========================

XmlIO.py: Generic Xml parser and I/O

Copyright (C) 2003 Rasmus Fogh (CCPN project)
 
=======================================================================

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
 
A copy of this license can be found in ../../../../license/LGPL.license.
 
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
 
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

======================COPYRIGHT/LICENSE END============================

To obtain more information about this code:

- CCPN website (http://www.bio.cam.ac.uk/nmr/ccp/)

- contact Rasmus Fogh (ccpn@bioc.cam.ac.uk)

=======================================================================

If you are using this software for academic purposes, we suggest
quoting the following reference:

===========================REFERENCE START=============================
R. Fogh, J. Ionides, E. Ulrich, W. Boucher, W. Vranken, J.P. Linge, M.
Habeck, W. Rieping, T.N. Bhat, J. Westbrook, K. Henrick, G. Gilliland,
H. Berman, J. Thornton, M. Nilges, J. Markley and E. Laue (2002). The
CCPN project: An interim report on a data model for the NMR community
(Progress report). Nature Struct. Biol. 9, 416-418.
===========================REFERENCE END===============================
 
"""

import os, time, types, gc

from memops.metamodel import MetaModel
from memops.metamodel import ImpConstants

from memops.universal.Constants import trueString, falseString

import memops.general.Implementation as implementation
ApiError = implementation.ApiError

from memops.general import Version

from memops.format.xml import Compatibility

from memops.api import Implementation as ImplementationApi
implPrefix = 'IMPL'

originatorString = "CCPN Python XmlIO"

indentBySpaces = 2
indents = {0:''}

# NBNB TBD version 1.1 statt 1.0?
xmlHeader1 = """<?xml version="1.0" encoding="UTF-8"?>
"""

#"""
xmlHeader2 = """<_StorageUnit time="%s" release="%s" 
 packageGuid="%s" originator="%s">
 
"""

xmlFooter = """
</_StorageUnit>
<!--End of Memops Data-->
"""

def getLoadingMaps(globalMapping, packageGuid, fileVersion):
  """ returns (mapping, loadMaps) tuple from a givne global mapping.
  Allows for backwards compatibility
  """
  
  # NBNB TBD add compatibility considerations
  for dd in globalMapping.values():
    if dd.get('guid') == packageGuid:
      mapping = dd
      break
  #
  return mapping, globalMapping['loadMaps']

class Dummy(object):
  """ Needed to provide sentinel objects
  """
  pass

def save(stream, topObject, mapping=None, 
         comment=None, simplified=False, compact=True): 
   """ wrapper function, to handle garbage collection for Xml export.
   """
   if gc.isenabled():
     gc.disable()
     try:
       doSave(stream, topObject, mapping, comment, simplified)
     finally:
       gc.enable()
   else:
     doSave(stream, topObject, mapping, comment, simplified)

def doSave(stream, topObject, mapping=None, 
           comment=None, simplified=False, compact=True): 
  """Write topObject and its descendants to open stream 'stream'.
  
  'mapping' contains all XMLtag-Model and Model-XMLtag mapping info.
  'comment' is added at the top of the file as an XML comment,
  If 'simplified' one-to-many links are written out at one end only,
  (otherwise at both ends), and attributes equal to the default are skipped.
  If 'compact' most hicard==1 attributes are written as XML attributes of
  the container, otherwise all attributes are written as elements.
  """
  
  # check for topObject
  isImplementation = False
  if topObject.memopsRoot is topObject:
    isImplementation = True
  elif not isinstance(topObject, ImplementationApi.TopObject):
    # neither MemopsRoot nor TopObjectTopObject
    raise ApiError("%s: is neither TopObject nor MemopsRoot" % topObject)
  
  # set up
  if mapping is None:
    selfPrefix = topObject.metaclass.container.shortName
    from memops.xml.Implementation import getGlobalMap
    mapping = getGlobalMap()[selfPrefix]
  else:
    selfPrefix = mapping['prefix']
    
  doComplex = not simplified
  nIndent = indentBySpaces
  # the 'strapp' name is from 'stream.append' - it sounds better
  strapp = stream.write
  
  # make sentinel objects
  startObj = Dummy()
  endObj = Dummy()
  
  reuseList = [None]
  emptyList = []
  
  # set object ID dictionary
  nextID = 1
  classIDs = {}
  
  strapp(xmlHeader1)
  
  if comment:
    strapp("<!--%s-->\n" % comment.replace('--','\-\-'))
    
  guid = mapping['guid']
  release = mapping['globalRelease']
  date = time.ctime(time.time())
  strapp(xmlHeader2 % (date, release, guid, originatorString))
  
  # set start indent
  nIndent = 0
  indent = ''
  
  # Write objects
  pName = topObject.metaclass.container.qualifiedName()
  print 'start generating %s output' % pName
  stack = [topObject]
  mapStack = [mapping['abstractTypes'][topObject.__class__.__name__]]
  while stack:
    
    val= stack.pop()
    curMap = mapStack.pop()
    
    if val is startObj:
      # start new attribute-less element 
      # NB we are cheating and putting a string on the mapStack 
      strapp("%s<%s>\n"%(indent, curMap))
      nIndent += indentBySpaces
      indent = (indents.get(nIndent)
                or indents.setdefault(nIndent, nIndent*' '))
    
    elif val is endObj:
      # end element
      # NB we are cheating and putting a string on the mapStack 
      nIndent -= indentBySpaces
      indent = indents[nIndent]
      
      strapp("%s</%s>\n"%(indent, curMap))
    
    else:
      typ = curMap.get('type')
      
      if typ == 'simple':
        toStr = curMap['toStr']
        tag = curMap['tag']
            
        if toStr == 'text':
          # text attributes
          val = val.replace( "&", "&amp;")
          val = val.replace("<", "&lt;")
          val = val.replace(">", "&gt;")
          
        else:
          # non-text simple attributes - toStr is conversion function
          val = toStr(val)
       
        strapp('%s<%s>%s</%s>\n' % (indent, tag, val, tag))
        
      elif typ == 'exo':
        tag = curMap['tag']
        stack.append(endObj)
        mapStack.append(tag)
        keyMaps = curMap['keyMaps']
        
        # put keys on stack
        keys = val.getFullKey(useGuid=True)
        for ii in range(len(keys)-1, -1, -1):
          key = keys[ii]
          stack.append(key)
          if (isinstance(key, ImplementationApi.MemopsObject) or 
              isinstance(key, ImplementationApi.MemopsDataTypeObject)):
            mapStack.append(keyMaps[ii][key.__class__.__name__])
          else:
            mapStack.append(keyMaps[ii])
        
        # could be done directly. 
        # But it will not make much difference, so for consistency.
        stack.append(startObj)
        mapStack.append(tag)
        
      else:
        # type class or cplx
        
        # set up
        ccpnDict = val.__dict__
        contDict = curMap['content']
        headerAttrs = curMap.get('headerAttrs', emptyList)
        optLinks = curMap.get('optLinks', emptyList)
        simpleAttrs = curMap.get('simpleAttrs', emptyList)
        cplxAttrs = curMap.get('cplxAttrs', emptyList)
        
 
        if typ == 'class':
          classtag = curMap['tag']
          
          # put end-of-object marker on stack
          stack.append(endObj)
          mapStack.append(classtag)
          
          # get  and write obj _ID
          _ID = classIDs.get(val)
          if _ID is None:
            _ID = nextID
            classIDs[val] = _ID
            nextID = nextID + 1
            
          strapp('%s<%s _ID="_%s"' % (indent, classtag, _ID))
        
        else:
          # typ == 'cplx'
          classtag = curMap['tag']
 
          # put end-of-object marker on stack
          stack.append(endObj)
          mapStack.append(classtag)
 
          strapp('%s<%s' % (indent, classtag))
 
        if compact:
          # set header attributes and links
          if doComplex and optLinks:
            names = headerAttrs + optLinks
          else:
            names = headerAttrs
 
          for name in names:
          
            tmpMap = contDict[name]
            
            if isImplementation and tmpMap.get('implSkip'):
              continue
            
            # NB hicard is always 1 here
            # NB here we use name instead of tag, as tag is 'Clazz.attr'
            value = ccpnDict[name]
 
            if value is not None:
              typ = tmpMap['type']
 
              if typ == 'link':
                _ID = classIDs.get(value)
                if _ID is None:
                  _ID = nextID
                  classIDs[value] = _ID
                  nextID = nextID + 1
                strapp(' %s="_%s"' % (tmpMap['name'], _ID))
 
              else:
                # typ == 'attr'
                if doComplex or value != tmpMap.get('default'):
                  toStr = tmpMap['data'].get('toStr')
                  if toStr == 'text':
                    # string type
                    value = value.replace( "&", "&amp;")
                    value = value.replace("\"", "&quot;")
                    value = value.replace("<", "&lt;")
                    value = value.replace(">", "&gt;")
 
                  else:
                    # non-string simple type
                    value = toStr(value)
 
                  strapp(' %s="%s"' % (tmpMap['name'], value))
 
        if compact and not simpleAttrs and not cplxAttrs:
          # class contains no XML elements. end immediately
          stack.pop()
          mapStack.pop()
          strapp('/>\n')
 
        else:
          # class may have more elements - process them
 
          # end start element
          strapp('>\n')
 
          # set indent
          nIndent += indentBySpaces
          indent = (indents.get(nIndent)
                    or indents.setdefault(nIndent, nIndent*' '))
 
          if compact:
            names = simpleAttrs
          elif doComplex and optLinks:
            names = headerAttrs + optLinks + simpleAttrs
          else:
            names = headerAttrs + simpleAttrs
 
          # set simple attributes and links
          for name in names:
            tmpMap = contDict[name]
            
            if isImplementation and tmpMap.get('implSkip'):
              continue
              
            val = ccpnDict[name]
            
            if doComplex or val != tmpMap.get('default'):
 
              if tmpMap['hicard'] == 1 and val is not None:
                # put here in case of (future) types
                reuseList[0] = val
                val = reuseList
 
              if val:
                tag = tmpMap['tag']
                typ = tmpMap['type']
 
                if typ == 'link':
                  ll = []
                  for value in val:
                    _ID = classIDs.get(value)
                    if _ID is None:
                      _ID = nextID
                      classIDs[value] = _ID
                      nextID = nextID + 1
                    ll.append(_ID)
                  strapp('%s<%s>_%s</%s>\n'
                         % (indent, tag, ' _'.join(str(x) for x in ll), tag))
 
                elif tmpMap.get('eType') == 'cplx':
                  # typ == 'attr', complex XML element
                  strapp("%s<%s>\n"%(indent, tag))
                  nIndent += indentBySpaces
                  indent = (indents.get(nIndent)
                            or indents.setdefault(nIndent, nIndent*' '))
 
                  tTag = tmpMap['data']['tag']
                  toStr = tmpMap['data']['toStr']
                  if toStr == 'text':
                    # string type
                    for value in val:
                      value = value.replace( "&", "&amp;")
                      value = value.replace("<", "&lt;")
                      value = value.replace(">", "&gt;")
                      strapp('%s<%s>%s</%s>\n' % (indent, tTag, value, tTag))
                  
                  else:
                    for value in val:
                      strapp('%s<%s>%s</%s>\n' 
                             % (indent, tTag, toStr(value), tTag))
                    
                  nIndent -= indentBySpaces
                  indent = indents[nIndent]
                  strapp("%s</%s>\n"%(indent, tag))
 
                else:
                  # typ == 'attr', simple XML element
                  toStr = tmpMap['data']['toStr']
                  if toStr == 'text':
                    # string type
                    ll = []
                    for value in val:
                      value = value.replace( "&", "&amp;")
                      value = value.replace("<", "&lt;")
                      value = value.replace(">", "&gt;")
                      ll.append(value)
                    strapp('%s<%s>%s</%s>\n' % (indent, tag, ' '.join(ll), tag))
 
                  else:
                    strapp('%s<%s>%s</%s>\n'
                           % (indent, tag, ' '.join(toStr(x) for x in val), tag))
 
          # put DataObjType attrs, and child links on stack
 
          names = cplxAttrs
          for name in names:
            val = ccpnDict[name]
 
            if val:
              tmpMap = contDict[name]
              
            
              if isImplementation and tmpMap.get('implSkip'):
                continue
              
              tag = tmpMap['tag']
              typ = tmpMap['type']
              tmpCont = tmpMap['content']
 
              stack.append(endObj)
              mapStack.append(tag)
              
              if tmpMap['hicard'] == 1:
                stack.append(val)
                mapStack.append(tmpCont[val.__class__.__name__])
 
              else:
                if typ == 'child':
                  items = val.items()
                  items.sort()
                  ll = [x[1] for x in items]
                else:
                  ll = list(val)
                ll.reverse() # for reproducibility
                  
                stack.extend(ll)
                mapStack.extend(tmpCont[x.__class__.__name__] for x in ll)
 
              stack.append(startObj)
              mapStack.append(tag)
 
  # we are finished now.
  strapp(xmlFooter)


def load(stream, topObject=None, globalMapping=None):
  """ Wrapper function, to handle garbage collection for Xml import.
  """
  if gc.isenabled():
    gc.disable()
    try:
      result = doLoad(stream, topObject, globalMapping)
    finally:
      gc.enable()
  else:
    result = doLoad(stream, topObject, globalMapping)
  #
  return result
   

def doLoad(stream, topObject=None, globalMapping=None):
  """ import Ccpn XML document using elementtree parser.
  
  globalMapping is the top level mappping dictionary that contains mappings
  for all packages. If not passed in, the current mapping will be imported 
  from memops.xml.Implementation.
  
  topObject can be None, the memopsRoot, or a package TopObject.
  If None the file must correspond to the Implementation package.
  If memopsRoot the file must be from a non-Implementaiton package, and the
  function will use the file to create a new TopObject under MemopsRoot.
  If a package TopObject the function will read the data from the file
  topObject into the passed-in TopObject, checking that the pckages match, 
  and giving a warning if the topObject (pseudo)key changes.
  
  Conceivably the rules could later be relaxed to allow a lower-level empty
  object  to be passed in for the purpose of reading subtrees. This would
  require some code modifications.
  
  
  NBNB TBD - the compatibility mechanism has been barely skethced in.
  It may well be changed once compatibility is implemented.
  """

  ####################################################################
  
  # get elementtree NBNB TBD to be redone to allow for different sources
  from elementtree import ElementTree
  
  delayedLoadData = []
  childLinkData = []
  objStack = []
  loadMaps = None
  objectDict = {}
  stdPriority = 100
  compatibility = {}
  topObjectKey = None
  parserState = 'starting'
  skipElement = None
  
  result = None
  newRoot = None
  
  # set up
  if globalMapping is None:
    from memops.xml.Implementation import getGlobalMap
    globalMapping = getGlobalMap()
    
  try:
    
    # needed for error handling
    elem = Dummy()
    elem.tag='dummyTag'
  
    for event, elem in ElementTree.iterparse(stream, events=("start", "end")):
 
      if skipElement:
        # we are skipping an element
        if elem is skipElement and event == 'end':
          skipElement = None
          elem.clear()
      
      elif event == "start":
        # element start
        tag = elem.tag
        
        if loadMaps:
          
          # get map and test
          try:
            curMap = loadMaps[tag]
          except KeyError:
            raise ApiError("no map found for element")
          except:
            raise ApiError("Load maps not set up correctly - should not get here")
            
          if curMap.get('skip'):
            # skip this element and go to end of loop
            skipElement = elem
            continue
            
          typ = curMap.get('type')
          
          if not objStack:
            # inside _storageUnit - preliminary check for TopObject element
            if typ == 'class' and topObjectKey is None:
              pass
            else:
              raise ApiError("_StorageUnit has more than one child element")
          
          if curMap.get('eType') == 'cplx':
          
            if typ in ('class', 'cplx'):
              # class, or cplx
 
              if typ == 'class':
 
                if topObjectKey is None:
                  # first topObject (passed in)
                  
                  if topObject is None:
                    if mapping['.prefix'] == implPrefix:
                      # reading Implementation.xml
                      topObjectKey = 'ignore'
                      obj = curMap['class'](isReading=True)
                      newRoot = obj
                    else:
                      raise ApiError(
                       "non-Implementation package %s called without TopObject"
                       % implPrefix
                      )
                  
                  elif topObject.memopsRoot is topObject:
                    # topObject is MemopsRoot
                    if mapping['.prefix'] == implPrefix:
                      raise ApiError(
                       "Attempt to load into pre-existing MemospRoot"
                      )
                    else:
                      # non-impl package - create TopObject afresh
                      topObjectKey = 'ignore'
                      obj = curMap['class'](topObject, isReading=True)
                      
                      # add to linkChild data
                      childLinkData.append(
                       topObject.__dict__[curMap['fromParent']]
                      )
                      childLinkData.append(curMap)
                      childLinkData.append(obj)
                      newRoot = obj
 
                  else:
                    # topObject should fit mapping
                    
                    if curMap['class'] is not topObject.__class__:
                      raise ApiError(
                       "TopObject class %s does not fit first element class %s"
                       % (topObject.__class__, curMap['class'])
                      )
                    if hasattr(topObject,'guid'):
                      if topObject.guid != elem.get('guid'):
                        raise ApiError(
                         "TopObject guid %s does not fit stored guid %s"
                         % (topObject.guid, elem.get('guid'))
                        )
                    if topObject.__dict__.get('isLoaded'):
                      raise ApiError(
                       "trying to load already loaded TopObject %s" % topObject
                      )
                    if topObject.__dict__.get('isModified'):
                      raise ApiError(
                       "trying to load already modified TopObject %s" 
                       % topObject
                      )
                      
                    # set up for continuing  
                    obj = topObject
                    # NB only MemopsRoot has fulKey None.
                    topObjectKey = topObject.getFullKey() or 'ignore'
                    obj.__dict__['isReading'] = True
                    
                    newRoot = None
                  
                  result = obj
                  topObjByGuid = obj.memopsRoot.__dict__['topObjects']
 
                  if objStack:
                    raise ApiError(
                     "Non-empty stack at TopObject - should not get here"
                    )
 
                else:
                  # make new class object
                  obj = curMap['class'](objStack[-1])
                  
                  # add data to childLinkData
                  childLinkData.append(
                   objStack[-1].__dict__[curMap['fromParent']]
                  )
                  childLinkData.append(curMap)
                  childLinkData.append(obj)
 
              elif typ == 'cplx':
                # make new dataObjType object
                obj = curMap['class'](override=True)
                objStack[-1].append(obj)
 
              else:
                raise ApiError("Unknown element type %s" % typ)
 
              # put obj on stack
              objStack.append(obj)
 
              # backwards compatibility
              cnvrt = curMap.get('cnvrt')
              if cnvrt is not None:
                priority = curMap.get('priority', stdPriority)
                ll = compatibility.get(priority)
                if ll is None:
                  ll = []
                  compatibility[priority] = ll
                ll.append(cnvrt)
                ll.append(obj)
 
              # treat XML attributes
              contMap = curMap['content']
              for tag, value in elem.items():
                if tag == '_ID':
                  objectDict[value] = obj
                else:
 
                  try:
                    tmpMap = contMap[tag]
                  except KeyError:
                    raise ApiError("no map found for XML attribute")
 
                  if not tmpMap.get('skip'):
                    typ = tmpMap['type']
 
                    if typ == 'link':
                      delayedLoadData.append(obj)
                      delayedLoadData.append((value,))
                      delayedLoadData.append(tmpMap)
 
                    else:
                      # types attr and text
                      cnvrt = tmpMap['data'].get('cnvrt')
                      if cnvrt != 'text':
                        value = cnvrt(value)
 
                      name = tmpMap['name']
 
                      if tmpMap.get('proc') == 'direct':
                        # set, bypassing API
                        obj.__dict__[name] = value
 
                      elif tmpMap.get('hicard') == 1:
                        # always, except when cardinality has changed
                        setattr(obj, name, value)
 
                      else:
                        # only when hicard has changed from 1 to something else
                        setattr(obj, name, [value])
 
            else:
              # exolink, collection (of exo, cplx, or attr (e.g. text))
              objStack.append([])
              
          # no action for : 'simple'. 'attr', 'link'
 
        else:
          # no map - at start or error
          if parserState == 'starting':
            if tag == '_StorageUnit':
              # first element
 
              # get version,  package, and updated mapping
              fileVersion = elem.get('release')
              packageGuid = elem.get('packageGuid')
              if fileVersion is None or packageGuid is None:
                raise ApiError(
                 " <_StorageUnit element lacks 'release' or 'packageGuid'"
                )
              fileVersion = Version.getVersion(fileVersion)
              mapping, loadMaps = getLoadingMaps(globalMapping, 
                                                 packageGuid, fileVersion)
 
              # state tracker - for tests an error messages
              parserState = 'reading'
 
            else:
              raise ApiError("no '_storageUnit' element found, or setup failed")
          else:
            raise ApiError("Read past end of _storageUnit ")
            
            
      else:
        # event == 'end', element end
        
        tag = elem.tag
        if tag == '_StorageUnit':
          break
        
        try:
          curMap = loadMaps[tag]
        except KeyError:
          raise ApiError("no map found for element")
        except:
          raise ApiError("Load maps not set up corerntly - should not get here")
            
        if objStack:
          # not yet finished 
          
          typ = curMap.get('type')
 
          if typ == 'simple':
            value = elem.text
            cnvrt = curMap.get('cnvrt')
            if cnvrt != 'text':
              value = cnvrt(value)
            objStack[-1].append(value)
          
          elif typ == 'link':
            delayedLoadData.append(objStack[-1])
            delayedLoadData.append(elem.text.split())
            delayedLoadData.append(curMap)
          
          elif typ in ('attr', 'coll'):
            name = curMap['name']
            hicard = curMap['hicard']
            
            if curMap.get('eType') == 'cplx':
              val = objStack.pop()
              
            else:
              toStr = curMap['data']['toStr']
              val = elem.text.split()
              if toStr != 'text':
                val = [toStr(x) for x in val]
            
            if not val:
              raise ApiError("XML element appears empty")
              
            if curMap.get('proc') == 'direct':
              # set, bypassing API - hicard must be 1.
              objStack[-1].__dict__[name] = val[0]
 
            elif hicard == 1:
              # std set, hicard == 1
              setattr(objStack[-1], name, val[0])
 
            elif hicard > 1:
              # std set, hicard != 1
              setattr(objStack[-1], name, val[:hicard])
 
            else:
              # std set, hicard == infinity
              setattr(objStack[-1], name, val)
          
          elif typ == 'child':
            if curMap.get('proc') == 'loadDelayed':
              # premature link dereferencing.
              # necessary to handle Impl package properly
              loadDelayedData(objectDict, delayedLoadData)
              linkChildData(childLinkData)
          
          else:
            # typ in ('exo', 'cplx', 'class')
          
            xx = objStack.pop()
            if not xx:
              raise ApiError("XML element appears empty")
            
            if typ == 'exo':
              if curMap.get('proc') != 'delay':
                top = topObjByGuid.get(xx[0])
                if len(xx) > 1:
                  objStack[-1].append(curMap['class'].getByKey(top, xx[1:]))
                else:
                  objStack[-1].append(top)
 
            elif typ == 'cplx':
              del xx.__dict__['override']
            
            # no action for 'class'
              
              
        elif tag != '_storageUnit':
          raise ApiError("objStack empty but element is not _storageUnit")
        
        # clean out to save memory
        elem.clear()
    
    else:
      raise ApiError("Premature end of file - no </_storageUnit> found")
      
    
    if objStack or skipElement:
      raise ApiError("""
Illegal state after parsing:
objStack length was %s
skipElement was %s""" % (len(objStack), skipElement))
    
    # delayed load
    parserState = 'postprocesing data'
    loadDelayedData(objectDict, delayedLoadData)
    
    # link children to parents
    linkChildData(childLinkData, newRoot=newRoot)
    
    if compatibility:
      # backwards compatibility
      parserState = 'handling version compatibility'
      doCompatibility(compatibility)
      
    # validity check
    parserState = 'checking validity'
    
    # unset isReading (NB - extra link is for future load of non-topObjects)
    result.topObject.__dict__['isReading'] = False
    result.topObject.__dict__['isLoaded'] = True
    
    if topObjectKey and topObjectKey != 'ignore':
      xx = result.getFullKey()
      if topObjectKey != xx:
        print ("WARNING TopObject key changed from %s to %s on reading"
               % (topObjectKey, xx))
               
    for obj in objectDict.values():
      obj.checkValid()
    
  except:
    # clean up in case of error.
    # Currently not done, as too hard to do properly. NBNB TBD
    if result is not None:
      result.topObject.__dict__['isReading'] = False
    
    print """Error loading file for %s. 
reading %s
last xml tag read: %s
parser state was: %s""" % (result, stream, elem.tag, parserState)
    if objStack:
      print "current object was: %s\n" % objStack[-1]
    else:
      print "Object stack was empty"
      
    raise
  #
  return result

def linkChildData(childLinkData, newRoot=None):
  """ set parent-child links"""
  
  topObjDict = None
  if newRoot is not None:
  
    memopsRoot = newRoot.memopsRoot
    if memopsRoot is newRoot:
      # MemopsRoot
      addToObjects = True
      topObjDict = memopsRoot.__dict__['topObjects']
      
    else:
      dd = memopsRoot.__dict__['topObjects']
      guid = newRoot.guid
      if guid in dd:
        raise ApiError("%s could not be created - guid %s already in use"
                       % (newRoot, guid))
      else:
        dd[guid] = newRoot
  
  #  reverse so that we set from the top
  childLinkData.reverse()
  while childLinkData:
    
    # get data
    parDict = childLinkData.pop()
    curMap = childLinkData.pop()
    obj = childLinkData.pop()
    
    # get key
    tag = curMap.get('objkey')
    if tag is None:
      key = obj.getLocalKey()
    else:
      key = obj.__dict__[tag]
    
    # add child link
    if key in parDict:
      raise ApiError("Cannot add %s child to %s - key %s already in use"
                     % (obj.qualifiedName, obj.parent, key))
    else:
      parDict[key] = obj
    
    # set TopObjects into TopObjects dictionary.
    # NB assumes that only TopObjects are here if the root parameter is not None
    if topObjDict is not None:
      guid = obj.guid
      if guid in topObjDict:
        raise ApiError("%s could not be created - guid %s already in use" 
                       % (obj, guid))
      else:
        topObjDict[guid] = obj
        
        # set to no longer reading
        obj.__dict__['isReading'] = False
      
      
def doCompatibility(compatibility):
  """ handle backwards compatibility
  """
    
  items = compatibility.items()
  
  # sort according to processing order (priority)
  items.sort()
  
  # do conversion
  for junk, stack in items:
    stack.reverse()
    obj = stack.pop()
    cnvrt = stack.pop()
    cnvrt(obj)


def loadDelayedData(objectDict, delayedLoadData):
  """ Load single links, and multiLinks, from delayedLoadData, 
  derefencing IDREFs as you go.
  """
  
  try:
     
    while delayedLoadData:
    
      curMap = delayedLoadData.pop()
      values = delayedLoadData.pop()
      obj = delayedLoadData.pop()
      
      name = curMap['name']
      hicard = curMap['hicard']
      
      ff = objectDict.get
      try:
        if hicard >= 1:
          values = [ff(x) for x in values[:hicard]]
        else:
          values = [ff(x) for x in values]
      except KeyError:
        raise ApiError("%s.%s: Linked-to object with _ID %s not found" 
                       % (obj, name, value))
                         
      if  hicard == 1:
        # std set, hicard == 1
        setattr(obj, name, values[0])
 
      else:
        # std set, hicard != 1
        setattr(obj, name, values)
         
  except:
    print "Error during delayed load. Object was %s\n" % obj
    print "values were: %s\n" % values
    print "tag name was: %s\n" % name
    raise
by Rasmus Fogh last modified 2007-05-10 12:18

Powered by Plone, the Open Source Content Management System

This site conforms to the following standards: