1 #---------------------------------------------------------------------------- 
   2 # Name:         xmlmarshaller.py 
   5 # Author:       John Spurling 
   9 # Copyright:    (c) 2004-2005 ActiveGrid, Inc. 
  10 # License:      wxWindows License 
  11 #---------------------------------------------------------------------------- 
  17 import xml
.sax
.handler
 
  18 import xml
.sax
.saxutils 
as saxutils
 
  19 from activegrid
.util
.lang 
import * 
  20 import activegrid
.util
.aglogging 
as aglogging
 
  22 MODULE_PATH 
= "__main__" 
  24 ## ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed 
  27 Special attributes that we recognize: 
  31 description: the name of the xml element for the marshalled object 
  33 name: __xmlattributes__ 
  35 description: the name(s) of the Lang string attribute(s) to be 
  36 marshalled as xml attributes instead of nested xml elements. currently 
  37 these can only be strings since there"s not a way to get the type 
  38 information back when unmarshalling. 
  42 description: the name(s) of the lang attribute(s) to skip when 
  47 description: describes an alternate Lang <-> XML name mapping.   
  48 Normally the name mapping is the identity function.  __xmlrename__ 
  49 overrides that.  The keys are the Lang names, the values are their 
  52 name: __xmlflattensequence__ 
  53 type: dict, tuple, or list 
  54 description: the name(s) of the Lang sequence attribute(s) whose 
  55 items are to be marshalled as a series of xml elements (with an 
  56 optional keyword argument that specifies the element name to use) as 
  57 opposed to containing them in a separate sequence element, e.g.: 
  60 <!-- normal way of marshalling --> 
  62   <item objtype="int">1</item> 
  63   <item objtype="int">2</item> 
  65 <!-- with __xmlflattensequence__ set to {"myseq": "squish"} --> 
  66 <squish objtype="int">1</squish> 
  67 <squish objtype="int">2</squish> 
  69 name: __xmlnamespaces__ 
  71 description: a dict of the namespaces that the object uses.  Each item 
  72 in the dict should consist of a prefix,url combination where the key is 
  73 the prefix and url is the value, e.g.: 
  75 __xmlnamespaces__ = { "xsd":"http://www.w3c.org/foo.xsd" } 
  77 name: __xmldefaultnamespace__ 
  79 description: the prefix of a namespace defined in __xmlnamespaces__ that 
  80 should be used as the default namespace for the object. 
  82 name: __xmlattrnamespaces__ 
  84 description: a dict assigning the Lang object"s attributes to the namespaces 
  85 defined in __xmlnamespaces__.  Each item in the dict should consist of a 
  86 prefix,attributeList combination where the key is the prefix and the value is 
  87 a list of the Lang attribute names.  e.g.: 
  89 __xmlattrnamespaces__ = { "ag":["firstName", "lastName", "addressLine1", "city"] } 
  91 name: __xmlattrgroups__ 
  93 description: a dict specifying groups of attributes to be wrapped in an enclosing tag. 
  94 The key is the name of the enclosing tag; the value is a list of attributes to include 
  97 __xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]} 
 101 global xmlMarshallerLogger
 
 102 xmlMarshallerLogger 
= logging
.getLogger("activegrid.util.xmlmarshaller.marshal") 
 103 xmlMarshallerLogger
.setLevel(aglogging
.LEVEL_WARN
) 
 104 # INFO  : low-level info 
 105 # DEBUG : debugging info 
 107 global knownGlobalTypes
 
 109 ################################################################################ 
 113 ################################################################################ 
 115 class Error(Exception): 
 116     """Base class for errors in this module.""" 
 119 class UnhandledTypeException(Error
): 
 120     """Exception raised when attempting to marshal an unsupported 
 123     def __init__(self
, typename
): 
 124         self
.typename 
= typename
 
 126         return "%s is not supported for marshalling." % str(self
.typename
) 
 128 class XMLAttributeIsNotStringType(Error
): 
 129     """Exception raised when an object"s attribute is specified to be 
 130     marshalled as an XML attribute of the enclosing object instead of 
 133     def __init__(self
, attrname
, typename
): 
 134         self
.attrname 
= attrname
 
 135         self
.typename 
= typename
 
 137         return """%s was set to be marshalled as an XML attribute 
 138         instead of a nested element, but the object"s type is %s, not 
 139         string.""" % (self
.attrname
, self
.typename
) 
 141 class MarshallerException(Exception): 
 144 ################################################################################ 
 148 ################################################################################ 
 151 XMLNS_PREFIX 
= XMLNS 
+ ":" 
 152 XMLNS_PREFIX_LENGTH 
= len(XMLNS_PREFIX
) 
 154 BASETYPE_ELEMENT_NAME 
= "item" 
 155 DICT_ITEM_NAME 
= "qqDictItem" 
 156 DICT_ITEM_KEY_NAME 
= "key" 
 157 DICT_ITEM_VALUE_NAME 
= "value" 
 159 # This list doesn"t seem to be used. 
 160 #   Internal documentation or useless? You make the call! 
 161 ##MEMBERS_TO_SKIP = ("__module__", "__doc__", "__xmlname__", "__xmlattributes__", 
 162 ##                   "__xmlexclude__", "__xmlflattensequence__", "__xmlnamespaces__", 
 163 ##                   "__xmldefaultnamespace__", "__xmlattrnamespaces__", 
 164 ##                   "__xmlattrgroups__") 
 166 ################################################################################ 
 168 # classes and functions 
 170 ################################################################################ 
 172 def setattrignorecase(object, name
, value
): 
 173     if (name 
not in object.__dict
__): 
 174         namelow 
= name
.lower() 
 175         for attr 
in object.__dict
__: 
 176             if attr
.lower() == namelow
: 
 177                 object.__dict
__[attr
] = value
 
 179     object.__dict
__[name
] = value
 
 181 def getComplexType(obj
): 
 182     if (hasattr(obj
, "__xsdcomplextype__")): 
 183         return obj
.__xsdcomplextype
__ 
 186 def _objectfactory(objname
, objargs
=None, xsname
=None): 
 187     "dynamically create an object based on the objname and return it." 
 189     if not isinstance(objargs
, list): 
 192 ##    print "[objectfactory] xsname [%s]; objname [%s]" % (xsname, objname) 
 194     # (a) deal with tagName:knownTypes mappings 
 196         objclass 
= knownGlobalTypes
.get(xsname
) 
 197         if (objclass 
!= None): 
 198             if (objargs 
!= None): 
 199                 return objclass(*objargs
) 
 203     # (b) next with intrinisic types 
 204     if objname 
== "str" or objname 
== "unicode": # don"t strip: blanks are significant 
 206             return saxutils
.unescape(objargs
[0]).encode() 
 209     elif objname 
== "bool": 
 210         return not objargs
[0].lower() == "false" 
 211     elif objname 
in ("float", "int", "long"): 
 212 ##        objargs = [x.strip() for x in objargs] 
 213         return __builtin__
.__dict
__[objname
](*objargs
) 
 214     elif objname 
== "None": 
 217     # (c) objtype=path...module.class    
 218     # split the objname into the typename and module path, 
 219     # importing the module if need be. 
 220 ##    print "[objectfactory] creating an object of type %s and value %s, xsname=%s" % (objname, objargs, xsname) 
 221     objtype 
= objname
.split(".")[-1] 
 222     pathlist 
= objname
.split(".") 
 223     modulename 
= ".".join(pathlist
[0:-1]) 
 224 ##    print "[objectfactory] object [%s] %s(%r)" % (objname, objtype, objargs) 
 228             module 
= __import__(modulename
) 
 229             for name 
in pathlist
[1:-1]: 
 230                 module 
= module
.__dict
__[name
] 
 231         elif __builtin__
.__dict
__.has_key(objname
): 
 234             raise MarshallerException("Could not find class %s" % objname
) 
 236             return module
.__dict
__[objtype
](*objargs
) 
 238             return module
.__dict
__[objtype
]() 
 240         raise MarshallerException("Could not find class %s" % objname
) 
 244     def __init__(self
, name
, attrs
=None): 
 250     def getobjtype(self
): 
 251         objtype 
= self
.attrs
.get("objtype") 
 252         if (objtype 
== None): 
 253             if (len(self
.children
) > 0): 
 260         print "    name = ", self
.name
, "; attrs = ", self
.attrs
, "number of children = ", len(self
.children
) 
 262         for child 
in self
.children
: 
 264             childClass 
= child
.__class
__.__name
__ 
 265             print "    Child ", i
, " class: ",childClass
 
 268 class XMLObjectFactory(xml
.sax
.ContentHandler
): 
 270         self
.rootelement 
= None 
 271         self
.elementstack 
= [] 
 272         xml
.sax
.handler
.ContentHandler
.__init
__(self
) 
 275         print "-----XMLObjectFactory Dump-------------------------------" 
 276         if (self
.rootelement 
== None): 
 277             print "rootelement is None" 
 279             print "rootelement is an object" 
 281         print "length of elementstack is: ", len(self
.elementstack
) 
 282         for e 
in self
.elementstack
: 
 284             print "elementstack[", i
, "]: " 
 286         print "-----end XMLObjectFactory--------------------------------" 
 288     ## ContentHandler methods 
 289     def startElement(self
, name
, attrs
): 
 290 ##        print "startElement for name: ", name 
 291         if name
.find(":") > -1:  # Strip namespace prefixes for now until actually looking them up in xsd 
 292             name 
= name
[name
.find(":") + 1:] 
 293 ##        for attrname in attrs.getNames(): 
 294 ##            print "%s: %s" % (attrname, attrs.getValue(attrname)) 
 295         element 
= Element(name
, attrs
.copy()) 
 296         self
.elementstack
.append(element
) 
 297 ##        print self.elementstack 
 299     def characters(self
, content
): 
 300 ##        print "got content: %s (%s)" % (content, type(content)) 
 301         if (content 
!= None): 
 302             self
.elementstack
[-1].content 
+= content
 
 304     def endElement(self
, name
): 
 305 ##        print "[endElement] name of element we"re at the end of: %s" % name 
 307         if name
.find(":") > -1:  # Strip namespace prefixes for now until actually looking them up in xsd 
 308             name 
= name
[name
.find(":") + 1:] 
 309         oldChildren 
= self
.elementstack
[-1].children
 
 310         element 
= self
.elementstack
.pop() 
 311         if ((len(self
.elementstack
) > 1) and (self
.elementstack
[-1].getobjtype() == "None")): 
 312             parentElement 
= self
.elementstack
[-2] 
 313 ##            print "[endElement] %s: found parent with objtype==None: using its grandparent" % name 
 314         elif (len(self
.elementstack
) > 0): 
 315             parentElement 
= self
.elementstack
[-1] 
 316         objtype 
= element
.getobjtype() 
 317 ##        print "element objtype is: ", objtype 
 318         if (objtype 
== "None"): 
 319 ##            print "[endElement] %s: skipping a (objtype==None) end tag" % name 
 321         constructorarglist 
= [] 
 322         if (len(element
.content
) > 0): 
 323             strippedElementContent 
= element
.content
.strip() 
 324             if (len(strippedElementContent
) > 0): 
 325                 constructorarglist
.append(element
.content
) 
 326 ##        print "[endElement] calling objectfactory" 
 327         obj 
= _objectfactory(objtype
, constructorarglist
, xsname
) 
 328         complexType 
= getComplexType(obj
) 
 330             if (hasattr(obj
, "__xmlname__") and getattr(obj
, "__xmlname__") == "sequence"): 
 331                 self
.elementstack
[-1].children 
= oldChildren
 
 333         if (len(element
.attrs
) > 0) and not isinstance(obj
, list): 
 334 ##            print "[endElement] %s: element has attrs and the obj is not a list" % name 
 335             for attrname
, attr 
in element
.attrs
.items(): 
 336                 if attrname 
== XMLNS 
or attrname
.startswith(XMLNS_PREFIX
): 
 337                     if attrname
.startswith(XMLNS_PREFIX
): 
 338                         ns 
= attrname
[XMLNS_PREFIX_LENGTH
:] 
 341                     if not hasattr(obj
, "__xmlnamespaces__"): 
 342                         obj
.__xmlnamespaces
__ = {ns:attr}
 
 343                     elif ns 
not in obj
.__xmlnamespaces
__: 
 344                         if (hasattr(obj
.__class
__, "__xmlnamespaces__")  
 345                             and (obj
.__xmlnamespaces
__ is obj
.__class
__.__xmlnamespaces
__)): 
 346                             obj
.__xmlnamespaces
__ = dict(obj
.__xmlnamespaces
__) 
 347                         obj
.__xmlnamespaces
__[ns
] = attr
 
 348                 elif not attrname 
== "objtype": 
 349                     if attrname
.find(":") > -1:  # Strip namespace prefixes for now until actually looking them up in xsd 
 350                         attrname 
= attrname
[attrname
.find(":") + 1:] 
 351                     if (complexType 
!= None): 
 352                         xsdElement 
= complexType
.findElement(attrname
) 
 353                         if (xsdElement 
!= None): 
 354                             type = xsdElement
.type 
 356                                 type = xsdToLangType(type) 
 357                                 ### ToDO remove maxOccurs hack after bug 177 is fixed 
 358                                 if attrname 
== "maxOccurs" and attr 
== "unbounded": 
 360                                 attr 
= _objectfactory(type, attr
) 
 362                         setattrignorecase(obj
, _toAttrName(obj
, attrname
), attr
) 
 363                     except AttributeError: 
 364                         raise MarshallerException("Error unmarshalling attribute \"%s\" of XML element \"%s\": object type not specified or known" % (attrname
, name
)) 
 365 ##                    obj.__dict__[_toAttrName(obj, attrname)] = attr 
 366         # stuff any child attributes meant to be in a sequence via the __xmlflattensequence__ 
 368         if hasattr(obj
, "__xmlflattensequence__"): 
 369             flatten 
= obj
.__xmlflattensequence
__ 
 370 ##            print "[endElement] %s: obj has __xmlflattensequence__" % name 
 371             if (isinstance(flatten
, dict)): 
 372 ##                print "[endElement]  dict with flatten.items: ", flatten.items() 
 373                 for sequencename
, xmlnametuple 
in flatten
.items(): 
 374                     if (xmlnametuple 
== None): 
 375                         flattenDict
[sequencename
] = sequencename
 
 376                     elif (not isinstance(xmlnametuple
, (tuple, list))): 
 377                         flattenDict
[str(xmlnametuple
)] = sequencename
 
 379                         for xmlname 
in xmlnametuple
: 
 380     ##                        print "[endElement]: adding flattenDict[%s] = %s" % (xmlname, sequencename) 
 381                             flattenDict
[xmlname
] = sequencename
 
 383                 raise "Invalid type for __xmlflattensequence___ : it must be a dict" 
 385         # reattach an object"s attributes to it 
 386         for childname
, child 
in element
.children
: 
 387 ##            print "[endElement] childname is: ", childname, "; child is: ", child 
 388             if (childname 
in flattenDict
): 
 389                 sequencename 
= _toAttrName(obj
, flattenDict
[childname
]) 
 390 ##                print "[endElement] sequencename is: ", sequencename 
 391                 if (not hasattr(obj
, sequencename
)): 
 392 ##                    print "[endElement] obj.__dict__ is: ", obj.__dict__ 
 393                     obj
.__dict
__[sequencename
] = [] 
 394                 sequencevalue 
= getattr(obj
, sequencename
) 
 395                 if (sequencevalue 
== None): 
 396                     obj
.__dict
__[sequencename
] = [] 
 397                     sequencevalue 
= getattr(obj
, sequencename
) 
 398                 sequencevalue
.append(child
) 
 399             elif (objtype 
== "list"): 
 401             elif isinstance(obj
, dict): 
 402                 if (childname 
== DICT_ITEM_NAME
): 
 403                     obj
[child
[DICT_ITEM_KEY_NAME
]] = child
[DICT_ITEM_VALUE_NAME
] 
 405                     obj
[childname
] = child
 
 407 ##                print "childname = %s, obj = %s, child = %s" % (childname, repr(obj), repr(child)) 
 409                     setattrignorecase(obj
, _toAttrName(obj
, childname
), child
) 
 410                 except AttributeError: 
 411                     raise MarshallerException("Error unmarshalling child element \"%s\" of XML element \"%s\": object type not specified or known" % (childname
, name
)) 
 412 ##                obj.__dict__[_toAttrName(obj, childname)] = child 
 414         if (complexType 
!= None): 
 415             for element 
in complexType
.elements
: 
 417                     elementName 
= _toAttrName(obj
, element
.name
) 
 418                     if ((elementName 
not in obj
.__dict
__) or (obj
.__dict
__[elementName
] == None)): 
 419                         langType 
= xsdToLangType(element
.type) 
 420                         defaultValue 
= _objectfactory(langType
, element
.default
) 
 421                         obj
.__dict
__[elementName
] = defaultValue
 
 424         if (isinstance(obj
, list)): 
 425             if ((element
.attrs
.has_key("mutable")) and (element
.attrs
.getValue("mutable") == "false")): 
 429         if (len(self
.elementstack
) > 0): 
 430 ##            print "[endElement] appending child with name: ", name, "; objtype: ", objtype 
 431             parentElement
.children
.append((name
, obj
)) 
 432 ##            print "parentElement now has ", len(parentElement.children), " children" 
 434             self
.rootelement 
= obj
 
 436     def getRootObject(self
): 
 437         return self
.rootelement
 
 439 def _toAttrName(obj
, name
): 
 440     if (hasattr(obj
, "__xmlrename__")): 
 441         for key
, val 
in obj
.__xmlrename
__.iteritems(): 
 445 ##    if (name.startswith("__") and not name.endswith("__")): 
 446 ##        name = "_%s%s" % (obj.__class__.__name__, name) 
 449 __typeMappingXsdToLang 
= { 
 453     "date": "str", # ToDO Need to work out how to create lang date types 
 455     "decimal": "float", # ToDO Does python have a better fixed point type? 
 461     "unicode": "unicode", 
 463     "duration": "str", # see above (date) 
 464     "datetime": "str", # see above (date) 
 465     "time": "str", # see above (date) 
 469 def xsdToLangType(xsdType
): 
 470     langType 
= __typeMappingXsdToLang
.get(xsdType
) 
 471     if (langType 
== None): 
 472         raise Exception("Unknown xsd type %s" % xsdType
) 
 475 def _getXmlValue(langValue
): 
 476     if (isinstance(langValue
, bool)): 
 477         return str(langValue
).lower() 
 478     elif (isinstance(langValue
, unicode)): 
 479         return langValue
.encode() 
 481         return str(langValue
) 
 483 def unmarshal(xmlstr
, knownTypes
=None): 
 484     global knownGlobalTypes
 
 485     if (knownTypes 
== None): 
 486         knownGlobalTypes 
= {} 
 488         knownGlobalTypes 
= knownTypes
 
 489     objectfactory 
= XMLObjectFactory() 
 490     xml
.sax
.parseString(xmlstr
, objectfactory
) 
 491     return objectfactory
.getRootObject() 
 494 def marshal(obj
, elementName
=None, prettyPrint
=False, indent
=0, knownTypes
=None, encoding
=-1): 
 495     xmlstr 
= "".join(_marshal(obj
, elementName
, prettyPrint
=prettyPrint
, indent
=indent
, knownTypes
=knownTypes
)) 
 496     if (isinstance(encoding
, basestring
)): 
 497         return '<?xml version="1.0" encoding="%s"?>\n%s' % (encoding
, xmlstr
.encode(encoding
)) 
 498     elif (encoding 
== None): 
 501         return '<?xml version="1.0" encoding="%s"?>\n%s' % (sys
.getdefaultencoding(), xmlstr
) 
 503 def _marshal(obj
, elementName
=None, nameSpacePrefix
="", nameSpaces
=None, prettyPrint
=False, indent
=0, knownTypes
=None): 
 504     xmlMarshallerLogger
.debug("--> _marshal: elementName=%s, type=%s, obj=%s" % (elementName
, type(obj
), str(obj
))) 
 506     if prettyPrint 
or indent
: 
 515     ## Determine the XML element name. If it isn"t specified in the 
 516     ## parameter list, look for it in the __xmlname__ Lang 
 517     ## attribute, else use the default generic BASETYPE_ELEMENT_NAME. 
 518     if not nameSpaces
: nameSpaces 
= {}  # Need to do this since if the {} 
is a default parameter it gets shared by all calls into the function
 
 520     if knownTypes 
== None: 
 522     if hasattr(obj
, "__xmlnamespaces__"): 
 523         for nameSpaceKey
, nameSpaceUrl 
in getattr(obj
, "__xmlnamespaces__").items(): 
 524             if nameSpaceUrl 
in asDict(nameSpaces
): 
 525                 nameSpaceKey 
= nameSpaces
[nameSpaceUrl
] 
 527 ##                # TODO: Wait to do this until there is shared for use when going through the object graph 
 528 ##                origNameSpaceKey = nameSpaceKey  # Make sure there is no key collision, ie: same key referencing two different URL"s 
 530 ##                while nameSpaceKey in nameSpaces.values(): 
 531 ##                    nameSpaceKey = origNameSpaceKey + str(i) 
 533                 nameSpaces
[nameSpaceUrl
] = nameSpaceKey
 
 534                 if nameSpaceKey 
== "": 
 535                     nameSpaceAttrs 
+= ' xmlns="%s" ' % (nameSpaceUrl
) 
 537                     nameSpaceAttrs 
+= ' xmlns:%s="%s" ' % (nameSpaceKey
, nameSpaceUrl
) 
 538         nameSpaceAttrs 
= nameSpaceAttrs
.rstrip() 
 539     if hasattr(obj
, "__xmldefaultnamespace__"): 
 540         nameSpacePrefix 
= getattr(obj
, "__xmldefaultnamespace__") + ":"         
 542         if hasattr(obj
, "__xmlname__"): 
 543             elementName 
= nameSpacePrefix 
+ obj
.__xmlname
__ 
 545             elementName 
= nameSpacePrefix 
+ BASETYPE_ELEMENT_NAME
 
 547         elementName 
= nameSpacePrefix 
+ elementName
 
 548     if hasattr(obj
, "__xmlsequencer__"): 
 549         elementAdd 
= obj
.__xmlsequencer
__ 
 553 ##    print "marshal: entered with elementName: ", elementName 
 555     ## Add more members_to_skip based on ones the user has selected 
 556     ## via the __xmlexclude__ attribute. 
 557     if hasattr(obj
, "__xmlexclude__"): 
 558 ##        print "marshal: found __xmlexclude__" 
 559         members_to_skip
.extend(obj
.__xmlexclude
__) 
 560     # Marshal the attributes that are selected to be XML attributes. 
 562     className 
= ag_className(obj
) 
 563     classNamePrefix 
= "_" + className
 
 564     if hasattr(obj
, "__xmlattributes__"): 
 565 ##        print "marshal: found __xmlattributes__" 
 566         xmlattributes 
= obj
.__xmlattributes
__ 
 567         members_to_skip
.extend(xmlattributes
) 
 568         for attr 
in xmlattributes
: 
 569             internalAttrName 
= attr
 
 571             if (attr
.startswith("__") and not attr
.endswith("__")):  
 572                 internalAttrName 
= classNamePrefix 
+ attr
 
 574             # Fail silently if a python attribute is specified to be 
 575             # an XML attribute but is missing. 
 576 ##            print "marshal:   processing attribute ", internalAttrName 
 578             value 
= attrs
.get(internalAttrName
) 
 580             complexType 
= getComplexType(obj
) 
 581             if (complexType 
!= None): 
 582 ##                print "marshal: found __xsdcomplextype__" 
 583                 xsdElement 
= complexType
.findElement(attr
) 
 584             if (xsdElement 
!= None): 
 585                 default 
= xsdElement
.default
 
 586                 if (default 
!= None): 
 587                     if ((default 
== value
) or (default 
== _getXmlValue(value
))): 
 595             # ToDO remove maxOccurs hack after bug 177 is fixed 
 596             if attr 
== "maxOccurs" and value 
== -1: 
 599             if isinstance(value
, bool): 
 605             attrNameSpacePrefix 
= "" 
 606             if hasattr(obj
, "__xmlattrnamespaces__"): 
 607 ##                print "marshal: found __xmlattrnamespaces__" 
 608                 for nameSpaceKey
, nameSpaceAttributes 
in getattr(obj
, "__xmlattrnamespaces__").iteritems(): 
 609                     if nameSpaceKey 
== nameSpacePrefix
[:-1]: # Don't need to specify attribute namespace if it is the same as its element 
 611                     if attr 
in nameSpaceAttributes
: 
 612                         attrNameSpacePrefix 
= nameSpaceKey 
+ ":" 
 614 ##            if attr.startswith("_"): 
 616             if (hasattr(obj
, "__xmlrename__") and attr 
in asDict(obj
.__xmlrename
__)): 
 617 ##                print "marshal: found __xmlrename__ (and its attribute)" 
 618                 attr 
= obj
.__xmlrename
__[attr
] 
 620             objattrs 
+= ' %s%s="%s"' % (attrNameSpacePrefix
, attr
, str(value
)) 
 621 ##            print "marshal:   new objattrs is: ", objattrs 
 625     elif isinstance(obj
, bool): 
 626         xmlString 
= ['%s<%s objtype="bool">%s</%s>%s' % (prefix
, elementName
, obj
, elementName
, newline
)] 
 627     elif isinstance(obj
, int): 
 628         xmlString 
= ['%s<%s objtype="int">%s</%s>%s' % (prefix
, elementName
, str(obj
), elementName
, newline
)] 
 629     elif isinstance(obj
, long): 
 630         xmlString 
= ['%s<%s objtype="long">%s</%s>%s' % (prefix
, elementName
, str(obj
), elementName
, newline
)] 
 631     elif isinstance(obj
, float): 
 632         xmlString 
= ['%s<%s objtype="float">%s</%s>%s' % (prefix
, elementName
, str(obj
), elementName
, newline
)] 
 633     elif isinstance(obj
, unicode): # have to check before basestring - unicode is instance of base string 
 634         xmlString 
= ['%s<%s>%s</%s>%s' % (prefix
, elementName
, saxutils
.escape(obj
.encode()), elementName
, newline
)] 
 635     elif isinstance(obj
, basestring
): 
 636         xmlString 
= ['%s<%s>%s</%s>%s' % (prefix
, elementName
, saxutils
.escape(obj
), elementName
, newline
)] 
 637     elif isinstance(obj
, list): 
 641             xmlString 
= ['%s<%s objtype="list">%s' % (prefix
, elementName
, newline
)] 
 643                 xmlString
.extend(_marshal(item
, nameSpaces
=nameSpaces
, indent
=indent
+increment
, knownTypes
=knownTypes
)) 
 644             xmlString
.append("%s</%s>%s" % (prefix
, elementName
, newline
)) 
 645     elif isinstance(obj
, tuple): 
 649             xmlString 
= ['%s<%s objtype="list" mutable="false">%s' % (prefix
, elementName
, newline
)] 
 651                 xmlString
.extend(_marshal(item
, nameSpaces
=nameSpaces
, indent
=indent
+increment
, knownTypes
=knownTypes
)) 
 652             xmlString
.append("%s</%s>%s" % (prefix
, elementName
, newline
)) 
 653     elif isinstance(obj
, dict): 
 654         xmlString 
= ['%s<%s objtype="dict">%s' % (prefix
, elementName
, newline
)] 
 655         subprefix 
= prefix 
+ " "*increment
 
 656         subindent 
= indent 
+ 2*increment
 
 657         for key
, val 
in obj
.iteritems(): 
 658 ##            if (isinstance(key, basestring) and key is legal identifier): 
 659 ##                xmlString.extend(_marshal(val, elementName=key, nameSpaces=nameSpaces, indent=subindent, knownTypes=knownTypes)) 
 661             xmlString
.append("%s<%s>%s" % (subprefix
, DICT_ITEM_NAME
, newline
)) 
 662             xmlString
.extend(_marshal(key
, elementName
=DICT_ITEM_KEY_NAME
, indent
=subindent
, knownTypes
=knownTypes
)) 
 663             xmlString
.extend(_marshal(val
, elementName
=DICT_ITEM_VALUE_NAME
, nameSpaces
=nameSpaces
, indent
=subindent
, knownTypes
=knownTypes
)) 
 664             xmlString
.append("%s</%s>%s" % (subprefix
, DICT_ITEM_NAME
, newline
)) 
 665         xmlString
.append("%s</%s>%s" % (prefix
, elementName
, newline
)) 
 667         # Only add the objtype if the element tag is unknown to us. 
 668         objname 
= knownTypes
.get(elementName
) 
 669         if (objname 
!= None): 
 670             xmlString 
= ["%s<%s%s%s" % (prefix
, elementName
, nameSpaceAttrs
, objattrs
)] 
 672             xmlString 
= ['%s<%s%s%s objtype="%s.%s"' % (prefix
, elementName
, nameSpaceAttrs
, objattrs
, obj
.__class
__.__module
__, className
)] 
 673         # get the member, value pairs for the object, filtering out the types we don"t support 
 674         if (elementAdd 
!= None): 
 675             prefix 
+= increment
*" " 
 679         if hasattr(obj
, "__xmlbody__"): 
 680             xmlbody 
= getattr(obj
, obj
.__xmlbody
__) 
 682                 xmlMemberString
.append(xmlbody
)            
 684             if hasattr(obj
, "__xmlattrgroups__"): 
 685                 attrGroups 
= obj
.__xmlattrgroups
__.copy() 
 686                 if (not isinstance(attrGroups
, dict)): 
 687                     raise "__xmlattrgroups__ is not a dict, but must be" 
 688                 for n 
in attrGroups
.iterkeys(): 
 689                     members_to_skip
.extend(attrGroups
[n
]) 
 692             # add the list of all attributes to attrGroups 
 693             eList 
= obj
.__dict
__.keys() 
 695             attrGroups
["__nogroup__"] = eList
 
 697             for eName
, eList 
in attrGroups
.iteritems(): 
 698                 if (eName 
!= "__nogroup__"): 
 699                     prefix 
+= increment
*" " 
 701                     xmlMemberString
.append('%s<%s objtype="None">%s' % (prefix
, eName
, newline
)) 
 703                     value 
= obj
.__dict
__[name
] 
 704                     if eName 
== "__nogroup__" and name 
in members_to_skip
: continue 
 705                     if name
.startswith("__") and name
.endswith("__"): continue 
 706                     subElementNameSpacePrefix 
= nameSpacePrefix
 
 707                     if hasattr(obj
, "__xmlattrnamespaces__"): 
 708                         for nameSpaceKey
, nameSpaceValues 
in getattr(obj
, "__xmlattrnamespaces__").iteritems(): 
 709                             if name 
in nameSpaceValues
: 
 710                                 subElementNameSpacePrefix 
= nameSpaceKey 
+ ":" 
 712                     # handle sequences listed in __xmlflattensequence__ 
 713                     # specially: instead of listing the contained items inside 
 714                     # of a separate list, as God intended, list them inside 
 715                     # the object containing the sequence. 
 716                     if (hasattr(obj
, "__xmlflattensequence__") and (value 
!= None) and (name 
in asDict(obj
.__xmlflattensequence
__))): 
 717                         xmlnametuple 
= obj
.__xmlflattensequence
__[name
] 
 718                         if (xmlnametuple 
== None): 
 719                             xmlnametuple 
= [name
] 
 720                         elif (not isinstance(xmlnametuple
, (tuple,list))): 
 721                             xmlnametuple 
= [str(xmlnametuple
)] 
 723                         if (len(xmlnametuple
) == 1): 
 724                             xmlname 
= xmlnametuple
[0] 
 726                         for seqitem 
in value
: 
 727 ##                            xmlname = xmlnametuple[ix] 
 729 ##                            if (ix >= len(xmlnametuple)): 
 731                             xmlMemberString
.extend(_marshal(seqitem
, xmlname
, subElementNameSpacePrefix
, nameSpaces
=nameSpaces
, indent
=indent
+increment
, knownTypes
=knownTypes
)) 
 733                         if (hasattr(obj
, "__xmlrename__") and name 
in asDict(obj
.__xmlrename
__)): 
 734                             xmlname 
= obj
.__xmlrename
__[name
] 
 737                         xmlMemberString
.extend(_marshal(value
, xmlname
, subElementNameSpacePrefix
, nameSpaces
=nameSpaces
, indent
=indent
+increment
, knownTypes
=knownTypes
)) 
 738                 if (eName 
!= "__nogroup__"): 
 739                     xmlMemberString
.append("%s</%s>%s" % (prefix
, eName
, newline
)) 
 740                     prefix 
= prefix
[:-increment
] 
 743         # if we have nested elements, add them here, otherwise close the element tag immediately. 
 745         for s 
in xmlMemberString
: 
 746             if (len(s
) > 0): newList
.append(s
) 
 747         xmlMemberString 
= newList
 
 748         if len(xmlMemberString
) > 0: 
 749             xmlString
.append(">") 
 750             if hasattr(obj
, "__xmlbody__"): 
 751                 xmlString
.extend(xmlMemberString
) 
 752                 xmlString
.append("</%s>%s" % (elementName
, newline
)) 
 754                 xmlString
.append(newline
) 
 755                 if (elementAdd 
!= None): 
 756                     xmlString
.append("%s<%s>%s" % (prefix
, elementAdd
, newline
)) 
 757                 xmlString
.extend(xmlMemberString
) 
 758                 if (elementAdd 
!= None): 
 759                     xmlString
.append("%s</%s>%s" % (prefix
, elementAdd
, newline
)) 
 760                     prefix 
= prefix
[:-increment
] 
 762                 xmlString
.append("%s</%s>%s" % (prefix
, elementName
, newline
)) 
 764             xmlString
.append("/>%s" % newline
) 
 766     xmlMarshallerLogger
.debug("<-- _marshal: %s" % str(xmlString
)) 
 769 # A simple test, to be executed when the xmlmarshaller is run standalone 
 770 class MarshallerPerson
: 
 771     __xmlname__ 
= "person" 
 772     __xmlexclude__ 
= ["fabulousness",] 
 773     __xmlattributes__ 
= ("nonSmoker",) 
 774     __xmlrename__ 
= {"_phoneNumber": "telephone"}
 
 775     __xmlflattensequence__ 
= {"favoriteWords": ("vocabulary",)}
 
 776     __xmlattrgroups__ 
= {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]}
 
 779         self
.firstName 
= "Albert" 
 780         self
.lastName 
= "Camus" 
 781         self
.addressLine1 
= "23 Absurd St." 
 785         self
._phoneNumber 
= "808-303-2323" 
 786         self
.favoriteWords 
= ["angst", "ennui", "existence"] 
 787         self
.phobias 
= ["war", "tuberculosis", "cars"] 
 789         self
.fabulousness 
= "tres tres" 
 790         self
.nonSmoker 
= False 
 793     p1 
= MarshallerPerson() 
 795     xmlP1 
= marshal(p1
, prettyPrint
=True, encoding
="utf-8")         
 796     print "\n########################" 
 797     print   "# testPerson test case #" 
 798     print   "########################" 
 800     p2 
= unmarshal(xmlP1
) 
 801     xmlP2 
= marshal(p2
, prettyPrint
=True, encoding
="utf-8") 
 803         print "Success: repeated marshalling yields identical results" 
 805         print "Failure: repeated marshalling yields different results"