]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/samples/ide/activegrid/util/xmlmarshaller.py
Added the ActiveGrid IDE as a sample application
[wxWidgets.git] / wxPython / samples / ide / activegrid / util / xmlmarshaller.py
diff --git a/wxPython/samples/ide/activegrid/util/xmlmarshaller.py b/wxPython/samples/ide/activegrid/util/xmlmarshaller.py
new file mode 100644 (file)
index 0000000..f305f0a
--- /dev/null
@@ -0,0 +1,649 @@
+#----------------------------------------------------------------------------
+# 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)