+#----------------------------------------------------------------------------
+# Name: xmlmarshaller.py
+# Purpose:
+#
+# Author: John Spurling
+#
+# Created: 7/28/04
+# CVS-ID: $Id$
+# Copyright: (c) 2004-2005 ActiveGrid, Inc.
+# License: wxWindows License
+#----------------------------------------------------------------------------
+from activegrid import util
+import inspect
+from types import *
+import xml.sax
+import xml.sax.handler
+import __builtin__
+from xml.sax import saxutils
+
+### ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed
+
+"""
+
+More documentation later, but here are some special Python attributes
+that McLane recognizes:
+
+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 Python 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 python attribute(s) to skip when
+marshalling.
+
+name: __xmlrename__
+type: dict
+description: describes an alternate Python <-> XML name mapping.
+Normally the name mapping is the identity function. __xmlrename__
+overrides that. The keys are the Python names, the values are their
+associated XML names.
+
+name: __xmlflattensequence__
+type: dict, tuple, or list
+description: the name(s) of the Python 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 Python 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 Python attribute names. e.g.:
+
+__xmlattrnamespaces__ = { "ag":["firstName", "lastName", "addressLine1", "city"] }
+
+
+"""
+
+################################################################################
+#
+# 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)
+
+################################################################################
+#
+# constants and such
+#
+################################################################################
+
+XMLNS = 'xmlns'
+XMLNS_PREFIX = XMLNS + ':'
+XMLNS_PREFIX_LENGTH = len(XMLNS_PREFIX)
+
+BASETYPE_ELEMENT_NAME = 'item'
+MEMBERS_TO_SKIP = ('__module__', '__doc__', '__xmlname__', '__xmlattributes__',
+ '__xmlexclude__', '__xmlflattensequence__', '__xmlnamespaces__',
+ '__xmldefaultnamespace__', '__xmlattrnamespaces__')
+
+WELL_KNOWN_OBJECTS = { "xs:element" : "activegrid.model.schema.XsdElement",
+ "xs:complexType" : "activegrid.model.schema.XsdComplexType",
+ "xs:complexType" : "activegrid.model.schema.XsdComplexType",
+ "xs:element" : "activegrid.model.schema.XsdElement",
+ "xs:key" : "activegrid.model.schema.XsdKey",
+ "xs:field" : "activegrid.model.schema.XsdKeyField",
+ "xs:keyref" : "activegrid.model.schema.XsdKeyRef",
+ "xs:selector" : "activegrid.model.schema.XsdKeySelector",
+ "xs:schema" : "activegrid.model.schema.Schema",
+ "ag:schemaOptions":"activegrid.model.schema.SchemaOptions",
+ "ag:debug" : "activegrid.model.processmodel.DebugOperation",
+ }
+
+
+################################################################################
+#
+# classes and functions
+#
+################################################################################
+
+def _objectfactory(objname, objargs=None, xsname=None):
+ try:
+ '''dynamically create an object based on the objname and return
+ it. look it up in the BASETYPE_ELEMENT_MAP first.
+ '''
+## print "_objectfactory creating an object of type %s and value %s, xsname=%s" % (objname, objargs, xsname)
+ # split the objname into the typename and module path,
+ # importing the module if need be.
+ if not isinstance(objargs, list):
+ objargs = [objargs]
+
+ if (xsname):
+ try:
+ objname = WELL_KNOWN_OBJECTS[xsname]
+ except KeyError:
+ pass
+
+ objtype = objname.split('.')[-1]
+ pathlist = objname.split('.')
+ modulename = '.'.join(pathlist[0:-1])
+
+## print "[objectfactory] objtype is %s" % objtype
+## print "[objectfactory] objargs is %s" % `objargs`
+
+ ## since the bool constructor will turn a string of non-zero
+ ## length into True, we call it with no argument (yielding a
+ ## False) if the string contains 'false'
+ if objtype == 'bool' and objargs[0].lower() == 'false':
+ objargs = None
+
+## if objtype == 'str':
+## print type(objargs)
+## print "string we're unescaping: '%s'" % objargs[0]
+## objargs = saxutils.unescape(objargs[0])
+ if objtype in ('float', 'int', 'str', 'long'):
+ objargs = [x.strip() for x in objargs]
+
+ if objtype == 'str':
+ objargs = [saxutils.unescape(x) for x in objargs]
+
+ if __builtin__.__dict__.has_key(objname):
+ module = __builtin__
+ else:
+ if modulename:
+ module = __import__(modulename)
+ for name in pathlist[1:-1]:
+ module = module.__dict__[name]
+ if objargs:
+ return module.__dict__[objtype](*objargs)
+ else:
+ if objtype == 'None':
+ return None
+ return module.__dict__[objtype]()
+ except KeyError:
+ raise KeyError("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):
+ if self.attrs.has_key('objtype'):
+ return self.attrs.getValue('objtype')
+ else:
+ return 'str'
+
+
+class XMLObjectFactory(xml.sax.ContentHandler):
+ def __init__(self):
+ self.rootelement = None
+ self.elementstack = []
+ xml.sax.handler.ContentHandler.__init__(self)
+
+ ## ContentHandler methods
+ def startElement(self, name, attrs):
+ if name.find(':') > -1: # Strip namespace prefixes for now until actually looking them up in xsd
+ name = name[name.index(':') + 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" % content
+ if content:
+ 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.index(':') + 1:]
+ element = self.elementstack.pop()
+ objtype = element.getobjtype()
+ constructorarglist = []
+ if element.content:
+ strippedElementContent = element.content.strip()
+ if strippedElementContent:
+ constructorarglist.append(element.content)
+ obj = _objectfactory(objtype, constructorarglist, xsname)
+ complexType = None
+ if hasattr(obj, '__xsdcomplextype__'):
+ complexType = getattr(obj, '__xsdcomplextype__')
+ if len(self.elementstack) > 0:
+ self.elementstack[-1].children.append((name, obj))
+ else:
+ self.rootelement = obj
+ if element.attrs and not isinstance(obj, list):
+ 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.index(':') + 1:]
+ if complexType:
+ xsdElement = complexType.findElement(attrname)
+ if xsdElement:
+ type = xsdElement.type
+ if type:
+ type = xsdToPythonType(type)
+ ### ToDO remove maxOccurs hack after bug 177 is fixed
+ if attrname == "maxOccurs" and attr == "unbounded":
+ attr = "-1"
+ attr = _objectfactory(type, attr)
+ util.setattrignorecase(obj, _toAttrName(obj, attrname), attr)
+## obj.__dict__[_toAttrName(obj, attrname)] = attr
+ # stuff any child attributes meant to be in a sequence via the __xmlflattensequence__
+ flattenDict = {}
+ if hasattr(obj, '__xmlflattensequence__'):
+ for sequencename, xmlnametuple in obj.__xmlflattensequence__.items():
+ for xmlname in xmlnametuple:
+ flattenDict[xmlname] = sequencename
+
+ # reattach an object's attributes to it
+ for childname, child in element.children:
+ if flattenDict.has_key(childname):
+ sequencename = _toAttrName(obj, flattenDict[childname])
+ try:
+ sequencevalue = obj.__dict__[sequencename]
+ except AttributeError:
+ sequencevalue = None
+ if sequencevalue == None:
+ sequencevalue = []
+ obj.__dict__[sequencename] = sequencevalue
+ sequencevalue.append(child)
+ elif isinstance(obj, list):
+ obj.append(child)
+ else:
+## print "childname = %s, obj = %s, child = %s" % (childname, repr(obj), repr(child))
+ util.setattrignorecase(obj, _toAttrName(obj, childname), child)
+## obj.__dict__[_toAttrName(obj, childname)] = child
+
+ if complexType:
+ for element in complexType.elements:
+ if element.default:
+ elementName = _toAttrName(obj, element.name)
+ if ((elementName not in obj.__dict__) or (obj.__dict__[elementName] == None)):
+ pythonType = xsdToPythonType(element.type)
+ defaultValue = _objectfactory(pythonType, element.default)
+ obj.__dict__[elementName] = defaultValue
+
+ 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
+
+__typeMappingXsdToPython = {
+ "string": "str",
+ "char": "str",
+ "varchar": "str",
+ "date": "str", # ToDO Need to work out how to create python 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",
+ }
+
+def xsdToPythonType(xsdType):
+ try:
+ return __typeMappingXsdToPython[xsdType]
+ except KeyError:
+ raise Exception("Unknown xsd type %s" % xsdType)
+
+def _getXmlValue(pythonValue):
+ if (isinstance(pythonValue, bool)):
+ return str(pythonValue).lower()
+ else:
+ return str(pythonValue)
+
+def unmarshal(xmlstr):
+ objectfactory = XMLObjectFactory()
+ xml.sax.parseString(xmlstr, objectfactory)
+ return objectfactory.getRootObject()
+
+
+def marshal(obj, elementName=None, nameSpacePrefix='', nameSpaces=None, prettyPrint=False, indent=0):
+ 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__ Python
+ ## 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 hasattr(obj, '__xmlnamespaces__'):
+ for nameSpaceKey, nameSpaceUrl in getattr(obj, '__xmlnamespaces__').items():
+ if nameSpaceUrl in nameSpaces:
+ nameSpaceKey = nameSpaces[nameSpaceUrl]
+ else:
+## # TODO: Wait to do this until there is shared state 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
+
+ members_to_skip = []
+ ## Add more members_to_skip based on ones the user has selected
+ ## via the __xmlexclude__ attribute.
+ if hasattr(obj, '__xmlexclude__'):
+ members_to_skip += list(obj.__xmlexclude__)
+ # Marshal the attributes that are selected to be XML attributes.
+ objattrs = ''
+ className = obj.__class__.__name__
+ classNamePrefix = "_" + className
+ if hasattr(obj, '__xmlattributes__'):
+ xmlattributes = obj.__xmlattributes__
+ members_to_skip += xmlattributes
+ for attr in xmlattributes:
+ internalAttrName = attr
+ if (attr.startswith("__") and not attr.endswith("__")):
+ internalAttrName = classNamePrefix + attr
+ # Fail silently if a python attribute is specified to be
+ # an XML attribute but is missing.
+ try:
+ value = obj.__dict__[internalAttrName]
+ except KeyError:
+ continue
+## # But, check and see if it is a property first:
+## if (hasPropertyValue(obj, attr)):
+## value = getattr(obj, attr)
+## else:
+## continue
+ xsdElement = None
+ if hasattr(obj, '__xsdcomplextype__'):
+ complexType = getattr(obj, '__xsdcomplextype__')
+ xsdElement = complexType.findElement(attr)
+ if xsdElement:
+ default = xsdElement.default
+ if default == value or default == _getXmlValue(value):
+ 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__'):
+ for nameSpaceKey, nameSpaceAttributes in getattr(obj, '__xmlattrnamespaces__').items():
+ if nameSpaceKey == nameSpacePrefix[:-1]: # Don't need to specify attribute namespace if it is the same as it selement
+ continue
+ if attr in nameSpaceAttributes:
+ attrNameSpacePrefix = nameSpaceKey + ':'
+ break
+## if attr.startswith('_'):
+## attr = attr[1:]
+ if (hasattr(obj, "__xmlrename__") and attr in obj.__xmlrename__):
+ attr = obj.__xmlrename__[attr]
+
+ objattrs += ' %s%s="%s"' % (attrNameSpacePrefix, attr, value)
+
+ objtype = type(obj)
+ if isinstance(obj, NoneType):
+ return ''
+# return '%s<%s objtype="None"/>%s' % (prefix, elementName, newline)
+ elif isinstance(obj, bool):
+ return '%s<%s objtype="bool">%s</%s>%s' % (prefix, elementName, obj, elementName, newline)
+ elif isinstance(obj, int):
+ return '''%s<%s objtype="int">%s</%s>%s''' % (prefix, elementName, str(obj), elementName, newline)
+ elif isinstance(obj, long):
+ return '%s<%s objtype="long">%s</%s>%s' % (prefix, elementName, str(obj), elementName, newline)
+ elif isinstance(obj, float):
+ return '%s<%s objtype="float">%s</%s>%s' % (prefix, elementName, str(obj), elementName, newline)
+ elif isinstance(obj, basestring):
+ return '''%s<%s>%s</%s>%s''' % (prefix, elementName, saxutils.escape(obj), elementName, newline)
+## elif isinstance(obj, unicode):
+## return '''%s<%s>%s</%s>%s''' % (prefix, elementName, obj, elementName, newline)
+ elif isinstance(obj, list):
+ if len(obj) < 1:
+ return ''
+ xmlString = '%s<%s objtype="list">%s' % (prefix, elementName, newline)
+ for item in obj:
+ xmlString += marshal(item, nameSpaces=nameSpaces, indent=indent+increment)
+ xmlString += '%s</%s>%s' % (prefix, elementName, newline)
+ return xmlString
+ elif isinstance(obj, tuple):
+ if len(obj) < 1:
+ return ''
+ xmlString = '%s<%s objtype="list" mutable="false">%s' % (prefix, elementName, newline)
+ for item in obj:
+ xmlString += marshal(item, nameSpaces=nameSpaces, indent=indent+increment)
+ xmlString += '%s</%s>%s' % (prefix, elementName, newline)
+ return xmlString
+ 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():
+ xmlString += "%s<key>%s%s%s</key>%s%s<value>%s%s%s</value>%s" \
+ % (subprefix, newline, marshal(key, indent=subindent), subprefix, newline, subprefix, newline, marshal(val, nameSpaces=nameSpaces, indent=subindent), subprefix, newline)
+ xmlString += '%s</%s>%s' % (prefix, elementName, newline)
+ return xmlString
+ else:
+ moduleName = obj.__class__.__module__
+ if (moduleName == "activegrid.model.schema"):
+ xmlString = '%s<%s%s%s' % (prefix, elementName, nameSpaceAttrs, objattrs)
+ else:
+ xmlString = '%s<%s%s%s objtype="%s.%s"' % (prefix, elementName, nameSpaceAttrs, objattrs, moduleName, className)
+ # get the member, value pairs for the object, filtering out
+ # the types we don't support.
+ xmlMemberString = ''
+ if hasattr(obj, '__xmlbody__'):
+ xmlMemberString = getattr(obj, obj.__xmlbody__)
+ else:
+ entryList = obj.__dict__.items()
+## # Add in properties
+## for key in obj.__class__.__dict__.iterkeys():
+## if (key not in members_to_skip and key not in obj.__dict__
+## and hasPropertyValue(obj, key)):
+## value = getattr(obj, key)
+## entryList.append((key, value))
+ entryList.sort()
+ for name, value in entryList:
+## # special name handling for private "__*" attributes:
+## # remove the _<class-name> added by Python
+## if name.startswith(classNamePrefix): name = name[len(classNamePrefix):]
+ if name in members_to_skip: continue
+ if name.startswith('__') and name.endswith('__'): continue
+## idx = name.find('__')
+## if idx > 0:
+## newName = name[idx+2:]
+## if newName:
+## name = newName
+ subElementNameSpacePrefix = nameSpacePrefix
+ if hasattr(obj, '__xmlattrnamespaces__'):
+ for nameSpaceKey, nameSpaceValues in getattr(obj, '__xmlattrnamespaces__').items():
+ 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 name in obj.__xmlflattensequence__ and value:
+ try:
+ xmlnametuple = obj.__xmlflattensequence__[name]
+ xmlname = None
+ if len(xmlnametuple) == 1:
+ xmlname = xmlnametuple[0]
+ except:
+ xmlname = name
+## xmlname = name.lower()
+ for seqitem in value:
+ xmlMemberString += marshal(seqitem, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment)
+ else:
+ if (hasattr(obj, "__xmlrename__") and name in obj.__xmlrename__):
+ xmlname = obj.__xmlrename__[name]
+ else:
+ xmlname = name
+## xmlname = name.lower()
+## # skip
+## if xmlname.startswith('_') and not xmlname.startswith('__'):
+## xmlname = xmlname[1:]
+## if (indent > 30):
+## print "getting pretty deep, xmlname = ", xmlname
+ xmlMemberString += marshal(value, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment)
+ # if we have nested elements, add them here, otherwise close the element tag immediately.
+ if xmlMemberString:
+ xmlString += '>'
+ if hasattr(obj, '__xmlbody__'):
+ xmlString += xmlMemberString
+ xmlString += '</%s>%s' % (elementName, newline)
+ else:
+ xmlString += newline
+ xmlString += xmlMemberString
+ xmlString += '%s</%s>%s' % (prefix, elementName, newline)
+ else:
+ xmlString = xmlString + '/>%s' % newline
+ return xmlString
+
+# We don't use this anymore but in case we want to get properties this is how
+# you do it
+def hasPropertyValue(obj, attr):
+ hasProp = False
+ try:
+ prop = obj.__class__.__dict__[attr]
+ if (isinstance(prop, property)):
+ hasProp = hasattr(obj, attr)
+ if (hasProp):
+ # It's a property and it has a value but sometimes we don't want it.
+ # If there is a _hasattr method execute it and the
+ # result will tell us whether to include this value
+ try:
+ hasProp = obj._hasattr(attr)
+ except:
+ pass
+ except KeyError:
+ pass
+ return hasProp
+
+if __name__ == '__main__':
+ from xmlmarshallertests import Person, marshalledint, marshalledlist
+
+ l = [1, 2, 3]
+ d = {'1': 1, '2': 2}
+ outerlist = [l]
+ xmlstr = marshal(d, "d", prettyPrint=True)
+ print xmlstr
+
+ person = Person()
+ person.firstName = "Albert"
+ person.lastName = "Camus"
+ person.addressLine1 = "23 Absurd St."
+ person.city = "Ennui"
+ person.state = "MO"
+ person.zip = "54321"
+ person._phoneNumber = "808-303-2323"
+ person.favoriteWords = ['angst', 'ennui', 'existence']
+ person.weight = 150
+
+ xmlstring = marshal(person, 'person', prettyPrint=True)
+ print xmlstring
+
+ obj = unmarshal(marshalledlist)
+ print "obj has type %s and value %s" % (type(obj), str(obj))
+ for item in obj:
+ print "item: %s" % str(item)