XmlIo.py
Python XmlIo functionality. The header comment is the most authoritative reference for Python Xml I/O
Size 36.1 kB - File type text/python-sourceFile 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( "&", "&")
val = val.replace("<", "<")
val = val.replace(">", ">")
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( "&", "&")
value = value.replace("\"", """)
value = value.replace("<", "<")
value = value.replace(">", ">")
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( "&", "&")
value = value.replace("<", "<")
value = value.replace(">", ">")
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( "&", "&")
value = value.replace("<", "<")
value = value.replace(">", ">")
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