Files
wxWidgets/wxPython/samples/ide/activegrid/util/xmlmarshaller.py
Robin Dunn 2eeaec1909 Docview and IDE patch from Morag Hua with fix for bug #1217890
"Closing view crashes Python" plus some new features:

    New feature added to the IDE is 'Extensions'.  Under
    Tools|Options|Extensions, you can add calls to external programs.
    For example you can add a "Notepad" extension (under windows) that
    will exec Notepad on the currently open file.  A new "Notepad"
    menu item will appear under the Tools menu.


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@34638 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2005-06-11 23:18:57 +00:00

807 lines
34 KiB
Python

#----------------------------------------------------------------------------
# Name: xmlmarshaller.py
# Purpose:
#
# Author: John Spurling
#
# Created: 7/28/04
# CVS-ID: $Id$
# Copyright: (c) 2004-2005 ActiveGrid, Inc.
# License: wxWindows License
#----------------------------------------------------------------------------
import __builtin__
import sys
from types import *
import logging
import xml.sax
import xml.sax.handler
import xml.sax.saxutils as saxutils
from activegrid.util.lang import *
import activegrid.util.aglogging as aglogging
MODULE_PATH = "__main__"
## ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed
"""
Special attributes that we recognize:
name: __xmlname__
type: string
description: the name of the xml element for the marshalled object
name: __xmlattributes__
type: tuple or list
description: the name(s) of the Lang string attribute(s) to be
marshalled as xml attributes instead of nested xml elements. currently
these can only be strings since there"s not a way to get the type
information back when unmarshalling.
name: __xmlexclude__
type: tuple or list
description: the name(s) of the lang attribute(s) to skip when
marshalling.
name: __xmlrename__
type: dict
description: describes an alternate Lang <-> XML name mapping.
Normally the name mapping is the identity function. __xmlrename__
overrides that. The keys are the Lang names, the values are their
associated XML names.
name: __xmlflattensequence__
type: dict, tuple, or list
description: the name(s) of the Lang sequence attribute(s) whose
items are to be marshalled as a series of xml elements (with an
optional keyword argument that specifies the element name to use) as
opposed to containing them in a separate sequence element, e.g.:
myseq = (1, 2)
<!-- normal way of marshalling -->
<myseq>
<item objtype="int">1</item>
<item objtype="int">2</item>
</myseq>
<!-- with __xmlflattensequence__ set to {"myseq": "squish"} -->
<squish objtype="int">1</squish>
<squish objtype="int">2</squish>
name: __xmlnamespaces__
type: dict
description: a dict of the namespaces that the object uses. Each item
in the dict should consist of a prefix,url combination where the key is
the prefix and url is the value, e.g.:
__xmlnamespaces__ = { "xsd":"http://www.w3c.org/foo.xsd" }
name: __xmldefaultnamespace__
type: String
description: the prefix of a namespace defined in __xmlnamespaces__ that
should be used as the default namespace for the object.
name: __xmlattrnamespaces__
type: dict
description: a dict assigning the Lang object"s attributes to the namespaces
defined in __xmlnamespaces__. Each item in the dict should consist of a
prefix,attributeList combination where the key is the prefix and the value is
a list of the Lang attribute names. e.g.:
__xmlattrnamespaces__ = { "ag":["firstName", "lastName", "addressLine1", "city"] }
name: __xmlattrgroups__
type: dict
description: a dict specifying groups of attributes to be wrapped in an enclosing tag.
The key is the name of the enclosing tag; the value is a list of attributes to include
within it. e.g.
__xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]}
"""
global xmlMarshallerLogger
xmlMarshallerLogger = logging.getLogger("activegrid.util.xmlmarshaller.marshal")
xmlMarshallerLogger.setLevel(aglogging.LEVEL_WARN)
# INFO : low-level info
# DEBUG : debugging info
global knownGlobalTypes
################################################################################
#
# module exceptions
#
################################################################################
class Error(Exception):
"""Base class for errors in this module."""
pass
class UnhandledTypeException(Error):
"""Exception raised when attempting to marshal an unsupported
type.
"""
def __init__(self, typename):
self.typename = typename
def __str__(self):
return "%s is not supported for marshalling." % str(self.typename)
class XMLAttributeIsNotStringType(Error):
"""Exception raised when an object"s attribute is specified to be
marshalled as an XML attribute of the enclosing object instead of
a nested element.
"""
def __init__(self, attrname, typename):
self.attrname = attrname
self.typename = typename
def __str__(self):
return """%s was set to be marshalled as an XML attribute
instead of a nested element, but the object"s type is %s, not
string.""" % (self.attrname, self.typename)
class MarshallerException(Exception):
pass
################################################################################
#
# constants and such
#
################################################################################
XMLNS = "xmlns"
XMLNS_PREFIX = XMLNS + ":"
XMLNS_PREFIX_LENGTH = len(XMLNS_PREFIX)
BASETYPE_ELEMENT_NAME = "item"
DICT_ITEM_NAME = "qqDictItem"
DICT_ITEM_KEY_NAME = "key"
DICT_ITEM_VALUE_NAME = "value"
# This list doesn"t seem to be used.
# Internal documentation or useless? You make the call!
##MEMBERS_TO_SKIP = ("__module__", "__doc__", "__xmlname__", "__xmlattributes__",
## "__xmlexclude__", "__xmlflattensequence__", "__xmlnamespaces__",
## "__xmldefaultnamespace__", "__xmlattrnamespaces__",
## "__xmlattrgroups__")
################################################################################
#
# classes and functions
#
################################################################################
def setattrignorecase(object, name, value):
if (name not in object.__dict__):
namelow = name.lower()
for attr in object.__dict__:
if attr.lower() == namelow:
object.__dict__[attr] = value
return
object.__dict__[name] = value
def getComplexType(obj):
if (hasattr(obj, "__xsdcomplextype__")):
return obj.__xsdcomplextype__
return None
def _objectfactory(objname, objargs=None, xsname=None):
"dynamically create an object based on the objname and return it."
if not isinstance(objargs, list):
objargs = [objargs]
## print "[objectfactory] xsname [%s]; objname [%s]" % (xsname, objname)
# (a) deal with tagName:knownTypes mappings
if (xsname != None):
objclass = knownGlobalTypes.get(xsname)
if (objclass != None):
if (objargs != None):
return objclass(*objargs)
else:
return objclass()
# (b) next with intrinisic types
if objname == "str" or objname == "unicode": # don"t strip: blanks are significant
if len(objargs) > 0:
return saxutils.unescape(objargs[0]).encode()
else:
return ""
elif objname == "bool":
return not objargs[0].lower() == "false"
elif objname in ("float", "int", "long"):
## objargs = [x.strip() for x in objargs]
return __builtin__.__dict__[objname](*objargs)
elif objname == "None":
return None
# (c) objtype=path...module.class
# split the objname into the typename and module path,
# importing the module if need be.
## print "[objectfactory] creating an object of type %s and value %s, xsname=%s" % (objname, objargs, xsname)
objtype = objname.split(".")[-1]
pathlist = objname.split(".")
modulename = ".".join(pathlist[0:-1])
## print "[objectfactory] object [%s] %s(%r)" % (objname, objtype, objargs)
try:
if modulename:
module = __import__(modulename)
for name in pathlist[1:-1]:
module = module.__dict__[name]
elif __builtin__.__dict__.has_key(objname):
module = __builtin__
else:
raise MarshallerException("Could not find class %s" % objname)
if objargs:
return module.__dict__[objtype](*objargs)
else:
return module.__dict__[objtype]()
except KeyError:
raise MarshallerException("Could not find class %s" % objname)
class Element:
def __init__(self, name, attrs=None):
self.name = name
self.attrs = attrs
self.content = ""
self.children = []
def getobjtype(self):
objtype = self.attrs.get("objtype")
if (objtype == None):
if (len(self.children) > 0):
objtype = "dict"
else:
objtype = "str"
return objtype
def __str__(self):
print " name = ", self.name, "; attrs = ", self.attrs, "number of children = ", len(self.children)
i = -1
for child in self.children:
i = i + 1
childClass = child.__class__.__name__
print " Child ", i, " class: ",childClass
class XMLObjectFactory(xml.sax.ContentHandler):
def __init__(self):
self.rootelement = None
self.elementstack = []
xml.sax.handler.ContentHandler.__init__(self)
def __str__(self):
print "-----XMLObjectFactory Dump-------------------------------"
if (self.rootelement == None):
print "rootelement is None"
else:
print "rootelement is an object"
i = -1
print "length of elementstack is: ", len(self.elementstack)
for e in self.elementstack:
i = i + 1
print "elementstack[", i, "]: "
str(e)
print "-----end XMLObjectFactory--------------------------------"
## ContentHandler methods
def startElement(self, name, attrs):
## print "startElement for name: ", name
if name.find(":") > -1: # Strip namespace prefixes for now until actually looking them up in xsd
name = name[name.find(":") + 1:]
## for attrname in attrs.getNames():
## print "%s: %s" % (attrname, attrs.getValue(attrname))
element = Element(name, attrs.copy())
self.elementstack.append(element)
## print self.elementstack
def characters(self, content):
## print "got content: %s (%s)" % (content, type(content))
if (content != None):
self.elementstack[-1].content += content
def endElement(self, name):
## print "[endElement] name of element we"re at the end of: %s" % name
xsname = name
if name.find(":") > -1: # Strip namespace prefixes for now until actually looking them up in xsd
name = name[name.find(":") + 1:]
oldChildren = self.elementstack[-1].children
element = self.elementstack.pop()
if ((len(self.elementstack) > 1) and (self.elementstack[-1].getobjtype() == "None")):
parentElement = self.elementstack[-2]
## print "[endElement] %s: found parent with objtype==None: using its grandparent" % name
elif (len(self.elementstack) > 0):
parentElement = self.elementstack[-1]
objtype = element.getobjtype()
## print "element objtype is: ", objtype
if (objtype == "None"):
## print "[endElement] %s: skipping a (objtype==None) end tag" % name
return
constructorarglist = []
if (len(element.content) > 0):
strippedElementContent = element.content.strip()
if (len(strippedElementContent) > 0):
constructorarglist.append(element.content)
## print "[endElement] calling objectfactory"
obj = _objectfactory(objtype, constructorarglist, xsname)
complexType = getComplexType(obj)
if (obj != None):
if (hasattr(obj, "__xmlname__") and getattr(obj, "__xmlname__") == "sequence"):
self.elementstack[-1].children = oldChildren
return
if (len(element.attrs) > 0) and not isinstance(obj, list):
## print "[endElement] %s: element has attrs and the obj is not a list" % name
for attrname, attr in element.attrs.items():
if attrname == XMLNS or attrname.startswith(XMLNS_PREFIX):
if attrname.startswith(XMLNS_PREFIX):
ns = attrname[XMLNS_PREFIX_LENGTH:]
else:
ns = ""
if not hasattr(obj, "__xmlnamespaces__"):
obj.__xmlnamespaces__ = {ns:attr}
elif ns not in obj.__xmlnamespaces__:
if (hasattr(obj.__class__, "__xmlnamespaces__")
and (obj.__xmlnamespaces__ is obj.__class__.__xmlnamespaces__)):
obj.__xmlnamespaces__ = dict(obj.__xmlnamespaces__)
obj.__xmlnamespaces__[ns] = attr
elif not attrname == "objtype":
if attrname.find(":") > -1: # Strip namespace prefixes for now until actually looking them up in xsd
attrname = attrname[attrname.find(":") + 1:]
if (complexType != None):
xsdElement = complexType.findElement(attrname)
if (xsdElement != None):
type = xsdElement.type
if (type != None):
type = xsdToLangType(type)
### ToDO remove maxOccurs hack after bug 177 is fixed
if attrname == "maxOccurs" and attr == "unbounded":
attr = "-1"
attr = _objectfactory(type, attr)
try:
setattrignorecase(obj, _toAttrName(obj, attrname), attr)
except AttributeError:
raise MarshallerException("Error unmarshalling attribute \"%s\" of XML element \"%s\": object type not specified or known" % (attrname, name))
## obj.__dict__[_toAttrName(obj, attrname)] = attr
# stuff any child attributes meant to be in a sequence via the __xmlflattensequence__
flattenDict = {}
if hasattr(obj, "__xmlflattensequence__"):
flatten = obj.__xmlflattensequence__
## print "[endElement] %s: obj has __xmlflattensequence__" % name
if (isinstance(flatten, dict)):
## print "[endElement] dict with flatten.items: ", flatten.items()
for sequencename, xmlnametuple in flatten.items():
if (xmlnametuple == None):
flattenDict[sequencename] = sequencename
elif (not isinstance(xmlnametuple, (tuple, list))):
flattenDict[str(xmlnametuple)] = sequencename
else:
for xmlname in xmlnametuple:
## print "[endElement]: adding flattenDict[%s] = %s" % (xmlname, sequencename)
flattenDict[xmlname] = sequencename
else:
raise "Invalid type for __xmlflattensequence___ : it must be a dict"
# reattach an object"s attributes to it
for childname, child in element.children:
## print "[endElement] childname is: ", childname, "; child is: ", child
if (childname in flattenDict):
sequencename = _toAttrName(obj, flattenDict[childname])
## print "[endElement] sequencename is: ", sequencename
if (not hasattr(obj, sequencename)):
## print "[endElement] obj.__dict__ is: ", obj.__dict__
obj.__dict__[sequencename] = []
sequencevalue = getattr(obj, sequencename)
if (sequencevalue == None):
obj.__dict__[sequencename] = []
sequencevalue = getattr(obj, sequencename)
sequencevalue.append(child)
elif (objtype == "list"):
obj.append(child)
elif isinstance(obj, dict):
if (childname == DICT_ITEM_NAME):
obj[child[DICT_ITEM_KEY_NAME]] = child[DICT_ITEM_VALUE_NAME]
else:
obj[childname] = child
else:
## print "childname = %s, obj = %s, child = %s" % (childname, repr(obj), repr(child))
try:
setattrignorecase(obj, _toAttrName(obj, childname), child)
except AttributeError:
raise MarshallerException("Error unmarshalling child element \"%s\" of XML element \"%s\": object type not specified or known" % (childname, name))
## obj.__dict__[_toAttrName(obj, childname)] = child
if (complexType != None):
for element in complexType.elements:
if element.default:
elementName = _toAttrName(obj, element.name)
if ((elementName not in obj.__dict__) or (obj.__dict__[elementName] == None)):
langType = xsdToLangType(element.type)
defaultValue = _objectfactory(langType, element.default)
obj.__dict__[elementName] = defaultValue
ifDefPy()
if (isinstance(obj, list)):
if ((element.attrs.has_key("mutable")) and (element.attrs.getValue("mutable") == "false")):
obj = tuple(obj)
endIfDef()
if (len(self.elementstack) > 0):
## print "[endElement] appending child with name: ", name, "; objtype: ", objtype
parentElement.children.append((name, obj))
## print "parentElement now has ", len(parentElement.children), " children"
else:
self.rootelement = obj
def getRootObject(self):
return self.rootelement
def _toAttrName(obj, name):
if (hasattr(obj, "__xmlrename__")):
for key, val in obj.__xmlrename__.iteritems():
if (name == val):
name = key
break
## if (name.startswith("__") and not name.endswith("__")):
## name = "_%s%s" % (obj.__class__.__name__, name)
return name
__typeMappingXsdToLang = {
"string": "str",
"char": "str",
"varchar": "str",
"date": "str", # ToDO Need to work out how to create lang date types
"boolean": "bool",
"decimal": "float", # ToDO Does python have a better fixed point type?
"int": "int",
"long": "long",
"float": "float",
"bool": "bool",
"str": "str",
"unicode": "unicode",
"short": "int",
"duration": "str", # see above (date)
"datetime": "str", # see above (date)
"time": "str", # see above (date)
"double": "float",
}
def xsdToLangType(xsdType):
langType = __typeMappingXsdToLang.get(xsdType)
if (langType == None):
raise Exception("Unknown xsd type %s" % xsdType)
return langType
def _getXmlValue(langValue):
if (isinstance(langValue, bool)):
return str(langValue).lower()
elif (isinstance(langValue, unicode)):
return langValue.encode()
else:
return str(langValue)
def unmarshal(xmlstr, knownTypes=None):
global knownGlobalTypes
if (knownTypes == None):
knownGlobalTypes = {}
else:
knownGlobalTypes = knownTypes
objectfactory = XMLObjectFactory()
xml.sax.parseString(xmlstr, objectfactory)
return objectfactory.getRootObject()
def marshal(obj, elementName=None, prettyPrint=False, indent=0, knownTypes=None, encoding=-1):
xmlstr = "".join(_marshal(obj, elementName, prettyPrint=prettyPrint, indent=indent, knownTypes=knownTypes))
if (isinstance(encoding, basestring)):
return '<?xml version="1.0" encoding="%s"?>\n%s' % (encoding, xmlstr.encode(encoding))
elif (encoding == None):
return xmlstr
else:
return '<?xml version="1.0" encoding="%s"?>\n%s' % (sys.getdefaultencoding(), xmlstr)
def _marshal(obj, elementName=None, nameSpacePrefix="", nameSpaces=None, prettyPrint=False, indent=0, knownTypes=None):
xmlMarshallerLogger.debug("--> _marshal: elementName=%s, type=%s, obj=%s" % (elementName, type(obj), str(obj)))
xmlString = None
if prettyPrint or indent:
prefix = " "*indent
newline = "\n"
increment = 4
else:
prefix = ""
newline = ""
increment = 0
## Determine the XML element name. If it isn"t specified in the
## parameter list, look for it in the __xmlname__ Lang
## attribute, else use the default generic BASETYPE_ELEMENT_NAME.
if not nameSpaces: nameSpaces = {} # Need to do this since if the {} is a default parameter it gets shared by all calls into the function
nameSpaceAttrs = ""
if knownTypes == None:
knownTypes = {}
if hasattr(obj, "__xmlnamespaces__"):
for nameSpaceKey, nameSpaceUrl in getattr(obj, "__xmlnamespaces__").items():
if nameSpaceUrl in asDict(nameSpaces):
nameSpaceKey = nameSpaces[nameSpaceUrl]
else:
## # TODO: Wait to do this until there is shared for use when going through the object graph
## origNameSpaceKey = nameSpaceKey # Make sure there is no key collision, ie: same key referencing two different URL"s
## i = 1
## while nameSpaceKey in nameSpaces.values():
## nameSpaceKey = origNameSpaceKey + str(i)
## i += 1
nameSpaces[nameSpaceUrl] = nameSpaceKey
if nameSpaceKey == "":
nameSpaceAttrs += ' xmlns="%s" ' % (nameSpaceUrl)
else:
nameSpaceAttrs += ' xmlns:%s="%s" ' % (nameSpaceKey, nameSpaceUrl)
nameSpaceAttrs = nameSpaceAttrs.rstrip()
if hasattr(obj, "__xmldefaultnamespace__"):
nameSpacePrefix = getattr(obj, "__xmldefaultnamespace__") + ":"
if not elementName:
if hasattr(obj, "__xmlname__"):
elementName = nameSpacePrefix + obj.__xmlname__
else:
elementName = nameSpacePrefix + BASETYPE_ELEMENT_NAME
else:
elementName = nameSpacePrefix + elementName
if hasattr(obj, "__xmlsequencer__"):
elementAdd = obj.__xmlsequencer__
else:
elementAdd = None
## print "marshal: entered with elementName: ", elementName
members_to_skip = []
## Add more members_to_skip based on ones the user has selected
## via the __xmlexclude__ attribute.
if hasattr(obj, "__xmlexclude__"):
## print "marshal: found __xmlexclude__"
members_to_skip.extend(obj.__xmlexclude__)
# Marshal the attributes that are selected to be XML attributes.
objattrs = ""
className = ag_className(obj)
classNamePrefix = "_" + className
if hasattr(obj, "__xmlattributes__"):
## print "marshal: found __xmlattributes__"
xmlattributes = obj.__xmlattributes__
members_to_skip.extend(xmlattributes)
for attr in xmlattributes:
internalAttrName = attr
ifDefPy()
if (attr.startswith("__") and not attr.endswith("__")):
internalAttrName = classNamePrefix + attr
endIfDef()
# Fail silently if a python attribute is specified to be
# an XML attribute but is missing.
## print "marshal: processing attribute ", internalAttrName
attrs = obj.__dict__
value = attrs.get(internalAttrName)
xsdElement = None
complexType = getComplexType(obj)
if (complexType != None):
## print "marshal: found __xsdcomplextype__"
xsdElement = complexType.findElement(attr)
if (xsdElement != None):
default = xsdElement.default
if (default != None):
if ((default == value) or (default == _getXmlValue(value))):
continue
else:
if (value == None):
continue
elif value == None:
continue
# ToDO remove maxOccurs hack after bug 177 is fixed
if attr == "maxOccurs" and value == -1:
value = "unbounded"
if isinstance(value, bool):
if value == True:
value = "true"
else:
value = "false"
attrNameSpacePrefix = ""
if hasattr(obj, "__xmlattrnamespaces__"):
## print "marshal: found __xmlattrnamespaces__"
for nameSpaceKey, nameSpaceAttributes in getattr(obj, "__xmlattrnamespaces__").iteritems():
if nameSpaceKey == nameSpacePrefix[:-1]: # Don't need to specify attribute namespace if it is the same as its element
continue
if attr in nameSpaceAttributes:
attrNameSpacePrefix = nameSpaceKey + ":"
break
## if attr.startswith("_"):
## attr = attr[1:]
if (hasattr(obj, "__xmlrename__") and attr in asDict(obj.__xmlrename__)):
## print "marshal: found __xmlrename__ (and its attribute)"
attr = obj.__xmlrename__[attr]
objattrs += ' %s%s="%s"' % (attrNameSpacePrefix, attr, str(value))
## print "marshal: new objattrs is: ", objattrs
if (obj == None):
xmlString = [""]
elif isinstance(obj, bool):
xmlString = ['%s<%s objtype="bool">%s</%s>%s' % (prefix, elementName, obj, elementName, newline)]
elif isinstance(obj, int):
xmlString = ['%s<%s objtype="int">%s</%s>%s' % (prefix, elementName, str(obj), elementName, newline)]
elif isinstance(obj, long):
xmlString = ['%s<%s objtype="long">%s</%s>%s' % (prefix, elementName, str(obj), elementName, newline)]
elif isinstance(obj, float):
xmlString = ['%s<%s objtype="float">%s</%s>%s' % (prefix, elementName, str(obj), elementName, newline)]
elif isinstance(obj, unicode): # have to check before basestring - unicode is instance of base string
xmlString = ['%s<%s>%s</%s>%s' % (prefix, elementName, saxutils.escape(obj.encode()), elementName, newline)]
elif isinstance(obj, basestring):
xmlString = ['%s<%s>%s</%s>%s' % (prefix, elementName, saxutils.escape(obj), elementName, newline)]
elif isinstance(obj, list):
if len(obj) < 1:
xmlString = ""
else:
xmlString = ['%s<%s objtype="list">%s' % (prefix, elementName, newline)]
for item in obj:
xmlString.extend(_marshal(item, nameSpaces=nameSpaces, indent=indent+increment, knownTypes=knownTypes))
xmlString.append("%s</%s>%s" % (prefix, elementName, newline))
elif isinstance(obj, tuple):
if len(obj) < 1:
xmlString = ""
else:
xmlString = ['%s<%s objtype="list" mutable="false">%s' % (prefix, elementName, newline)]
for item in obj:
xmlString.extend(_marshal(item, nameSpaces=nameSpaces, indent=indent+increment, knownTypes=knownTypes))
xmlString.append("%s</%s>%s" % (prefix, elementName, newline))
elif isinstance(obj, dict):
xmlString = ['%s<%s objtype="dict">%s' % (prefix, elementName, newline)]
subprefix = prefix + " "*increment
subindent = indent + 2*increment
for key, val in obj.iteritems():
## if (isinstance(key, basestring) and key is legal identifier):
## xmlString.extend(_marshal(val, elementName=key, nameSpaces=nameSpaces, indent=subindent, knownTypes=knownTypes))
## else:
xmlString.append("%s<%s>%s" % (subprefix, DICT_ITEM_NAME, newline))
xmlString.extend(_marshal(key, elementName=DICT_ITEM_KEY_NAME, indent=subindent, knownTypes=knownTypes))
xmlString.extend(_marshal(val, elementName=DICT_ITEM_VALUE_NAME, nameSpaces=nameSpaces, indent=subindent, knownTypes=knownTypes))
xmlString.append("%s</%s>%s" % (subprefix, DICT_ITEM_NAME, newline))
xmlString.append("%s</%s>%s" % (prefix, elementName, newline))
else:
# Only add the objtype if the element tag is unknown to us.
objname = knownTypes.get(elementName)
if (objname != None):
xmlString = ["%s<%s%s%s" % (prefix, elementName, nameSpaceAttrs, objattrs)]
else:
xmlString = ['%s<%s%s%s objtype="%s.%s"' % (prefix, elementName, nameSpaceAttrs, objattrs, obj.__class__.__module__, className)]
# get the member, value pairs for the object, filtering out the types we don"t support
if (elementAdd != None):
prefix += increment*" "
indent += increment
xmlMemberString = []
if hasattr(obj, "__xmlbody__"):
xmlbody = getattr(obj, obj.__xmlbody__)
if xmlbody != None:
xmlMemberString.append(xmlbody)
else:
if hasattr(obj, "__xmlattrgroups__"):
attrGroups = obj.__xmlattrgroups__.copy()
if (not isinstance(attrGroups, dict)):
raise "__xmlattrgroups__ is not a dict, but must be"
for n in attrGroups.iterkeys():
members_to_skip.extend(attrGroups[n])
else:
attrGroups = {}
# add the list of all attributes to attrGroups
eList = obj.__dict__.keys()
eList.sort()
attrGroups["__nogroup__"] = eList
for eName, eList in attrGroups.iteritems():
if (eName != "__nogroup__"):
prefix += increment*" "
indent += increment
xmlMemberString.append('%s<%s objtype="None">%s' % (prefix, eName, newline))
for name in eList:
value = obj.__dict__[name]
if eName == "__nogroup__" and name in members_to_skip: continue
if name.startswith("__") and name.endswith("__"): continue
subElementNameSpacePrefix = nameSpacePrefix
if hasattr(obj, "__xmlattrnamespaces__"):
for nameSpaceKey, nameSpaceValues in getattr(obj, "__xmlattrnamespaces__").iteritems():
if name in nameSpaceValues:
subElementNameSpacePrefix = nameSpaceKey + ":"
break
# handle sequences listed in __xmlflattensequence__
# specially: instead of listing the contained items inside
# of a separate list, as God intended, list them inside
# the object containing the sequence.
if (hasattr(obj, "__xmlflattensequence__") and (value != None) and (name in asDict(obj.__xmlflattensequence__))):
xmlnametuple = obj.__xmlflattensequence__[name]
if (xmlnametuple == None):
xmlnametuple = [name]
elif (not isinstance(xmlnametuple, (tuple,list))):
xmlnametuple = [str(xmlnametuple)]
xmlname = None
if (len(xmlnametuple) == 1):
xmlname = xmlnametuple[0]
## ix = 0
for seqitem in value:
## xmlname = xmlnametuple[ix]
## ix += 1
## if (ix >= len(xmlnametuple)):
## ix = 0
xmlMemberString.extend(_marshal(seqitem, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment, knownTypes=knownTypes))
else:
if (hasattr(obj, "__xmlrename__") and name in asDict(obj.__xmlrename__)):
xmlname = obj.__xmlrename__[name]
else:
xmlname = name
xmlMemberString.extend(_marshal(value, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment, knownTypes=knownTypes))
if (eName != "__nogroup__"):
xmlMemberString.append("%s</%s>%s" % (prefix, eName, newline))
prefix = prefix[:-increment]
indent -= increment
# if we have nested elements, add them here, otherwise close the element tag immediately.
newList = []
for s in xmlMemberString:
if (len(s) > 0): newList.append(s)
xmlMemberString = newList
if len(xmlMemberString) > 0:
xmlString.append(">")
if hasattr(obj, "__xmlbody__"):
xmlString.extend(xmlMemberString)
xmlString.append("</%s>%s" % (elementName, newline))
else:
xmlString.append(newline)
if (elementAdd != None):
xmlString.append("%s<%s>%s" % (prefix, elementAdd, newline))
xmlString.extend(xmlMemberString)
if (elementAdd != None):
xmlString.append("%s</%s>%s" % (prefix, elementAdd, newline))
prefix = prefix[:-increment]
indent -= increment
xmlString.append("%s</%s>%s" % (prefix, elementName, newline))
else:
xmlString.append("/>%s" % newline)
## return xmlString
xmlMarshallerLogger.debug("<-- _marshal: %s" % str(xmlString))
return xmlString
# A simple test, to be executed when the xmlmarshaller is run standalone
class MarshallerPerson:
__xmlname__ = "person"
__xmlexclude__ = ["fabulousness",]
__xmlattributes__ = ("nonSmoker",)
__xmlrename__ = {"_phoneNumber": "telephone"}
__xmlflattensequence__ = {"favoriteWords": ("vocabulary",)}
__xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]}
def setPerson(self):
self.firstName = "Albert"
self.lastName = "Camus"
self.addressLine1 = "23 Absurd St."
self.city = "Ennui"
self.state = "MO"
self.zip = "54321"
self._phoneNumber = "808-303-2323"
self.favoriteWords = ["angst", "ennui", "existence"]
self.phobias = ["war", "tuberculosis", "cars"]
self.weight = 150
self.fabulousness = "tres tres"
self.nonSmoker = False
if isMain(__name__):
p1 = MarshallerPerson()
p1.setPerson()
xmlP1 = marshal(p1, prettyPrint=True, encoding="utf-8")
print "\n########################"
print "# testPerson test case #"
print "########################"
print xmlP1
p2 = unmarshal(xmlP1)
xmlP2 = marshal(p2, prettyPrint=True, encoding="utf-8")
if xmlP1 == xmlP2:
print "Success: repeated marshalling yields identical results"
else:
print "Failure: repeated marshalling yields different results"
print xmlP2