1 #---------------------------------------------------------------------------- 
   2 # Name:         xmlmarshaller.py 
   5 # Authors:       John Spurling, Joel Hare, Alan Mullendore 
   9 # Copyright:    (c) 2004-2005 ActiveGrid, Inc. 
  10 # License:      wxWindows License 
  11 #---------------------------------------------------------------------------- 
  14 from activegrid
.util
.lang 
import * 
  18 import xml
.sax
.handler
 
  20 import xml
.sax
.saxutils 
as saxutils
 
  21 import activegrid
.util
.objutils 
as objutils
 
  22 import activegrid
.util
.aglogging 
as aglogging
 
  24 MODULE_PATH 
= "__main__" 
  26 ## ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed 
  29 Special attributes that we recognize: 
  33 description: the name of the xml element for the marshalled object 
  35 name: __xmlattributes__ 
  37 description: the name(s) of the Lang string attribute(s) to be 
  38 marshalled as xml attributes instead of nested xml elements. currently 
  39 these can only be strings since there"s not a way to get the type 
  40 information back when unmarshalling. 
  44 description: the name(s) of the lang attribute(s) to skip when 
  49 description: describes an alternate Lang <-> XML name mapping.   
  50 Normally the name mapping is the identity function.  __xmlrename__ 
  51 overrides that.  The keys are the Lang names, the values are their 
  54 name: __xmlflattensequence__ 
  55 type: dict, tuple, or list 
  56 description: the name(s) of the Lang sequence attribute(s) whose 
  57 items are to be marshalled as a series of xml elements (with an 
  58 optional keyword argument that specifies the element name to use) as 
  59 opposed to containing them in a separate sequence element, e.g.: 
  62 <!-- normal way of marshalling --> 
  64   <item objtype="int">1</item> 
  65   <item objtype="int">2</item> 
  67 <!-- with __xmlflattensequence__ set to {"myseq": "squish"} --> 
  68 <squish objtype="int">1</squish> 
  69 <squish objtype="int">2</squish> 
  71 name: __xmlnamespaces__ 
  73 description: a dict of the namespaces that the object uses.  Each item 
  74 in the dict should consist of a prefix,url combination where the key is 
  75 the prefix and url is the value, e.g.: 
  77 __xmlnamespaces__ = { "xsd":"http://www.w3c.org/foo.xsd" } 
  79 name: __xmldefaultnamespace__ 
  81 description: the prefix of a namespace defined in __xmlnamespaces__ that 
  82 should be used as the default namespace for the object. 
  84 name: __xmlattrnamespaces__ 
  86 description: a dict assigning the Lang object"s attributes to the namespaces 
  87 defined in __xmlnamespaces__.  Each item in the dict should consist of a 
  88 prefix,attributeList combination where the key is the prefix and the value is 
  89 a list of the Lang attribute names.  e.g.: 
  91 __xmlattrnamespaces__ = { "ag":["firstName", "lastName", "addressLine1", "city"] } 
  93 name: __xmlattrgroups__ 
  95 description: a dict specifying groups of attributes to be wrapped in an enclosing tag. 
  96 The key is the name of the enclosing tag; the value is a list of attributes to include 
  99 __xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]} 
 101 name: __xmlcdatacontent__ 
 103 description: value is the name of a string attribute that should be assigned CDATA content from the 
 104 source document and that should be marshalled as CDATA. 
 106 __xmlcdatacontent__ = "messyContent" 
 110 global xmlMarshallerLogger
 
 111 xmlMarshallerLogger 
= logging
.getLogger("activegrid.util.xmlmarshaller.marshal") 
 112 xmlMarshallerLogger
.setLevel(aglogging
.LEVEL_WARN
) 
 113 # INFO  : low-level info 
 114 # DEBUG : debugging info 
 116 ################################################################################ 
 120 ################################################################################ 
 122 class Error(Exception): 
 123     """Base class for errors in this module.""" 
 126 class UnhandledTypeException(Error
): 
 127     """Exception raised when attempting to marshal an unsupported 
 130     def __init__(self
, typename
): 
 131         self
.typename 
= typename
 
 133         return "%s is not supported for marshalling." % str(self
.typename
) 
 135 class XMLAttributeIsNotStringType(Error
): 
 136     """Exception raised when an object"s attribute is specified to be 
 137     marshalled as an XML attribute of the enclosing object instead of 
 140     def __init__(self
, attrname
, typename
): 
 141         self
.attrname 
= attrname
 
 142         self
.typename 
= typename
 
 144         return """%s was set to be marshalled as an XML attribute 
 145         instead of a nested element, but the object"s type is %s, not 
 146         string.""" % (self
.attrname
, self
.typename
) 
 148 class MarshallerException(Exception): 
 151 class UnmarshallerException(Exception): 
 154 ################################################################################ 
 158 ################################################################################ 
 161 XMLNS_PREFIX 
= XMLNS 
+ ":" 
 162 XMLNS_PREFIX_LENGTH 
= len(XMLNS_PREFIX
) 
 163 DEFAULT_NAMESPACE_KEY 
= "__DEFAULTNS__" 
 165 XMLSCHEMA_XSD_URL 
= "http://www.w3.org/2001/XMLSchema" 
 166 AG_URL 
= "http://www.activegrid.com/ag.xsd" 
 168 BASETYPE_ELEMENT_NAME 
= "item" 
 169 DICT_ITEM_NAME 
= "qqDictItem" 
 170 DICT_ITEM_KEY_NAME 
= "key" 
 171 DICT_ITEM_VALUE_NAME 
= "value" 
 173 # This list doesn"t seem to be used. 
 174 #   Internal documentation or useless? You make the call! 
 175 ##MEMBERS_TO_SKIP = ("__module__", "__doc__", "__xmlname__", "__xmlattributes__", 
 176 ##                   "__xmlexclude__", "__xmlflattensequence__", "__xmlnamespaces__", 
 177 ##                   "__xmldefaultnamespace__", "__xmlattrnamespaces__", 
 178 ##                   "__xmlattrgroups__") 
 180 ################################################################################ 
 182 # classes and functions 
 184 ################################################################################ 
 186 def setattrignorecase(object, name
, value
): 
 187     if (name 
not in object.__dict
__): 
 188         namelow 
= name
.lower() 
 189         for attr 
in object.__dict
__: 
 190             if attr
.lower() == namelow
: 
 191                 object.__dict
__[attr
] = value
 
 193     object.__dict
__[name
] = value
 
 195 def getComplexType(obj
): 
 196     if (hasattr(obj
, "__xsdcomplextype__")): 
 197         return obj
.__xsdcomplextype
__ 
 200 def _objectfactory(objname
, objargs
=None, objclass
=None): 
 201     "dynamically create an object based on the objname and return it." 
 202 ##    print "[objectfactory] objname [%s]" % (objname) 
 203     if not isinstance(objargs
, list): 
 205     if (objclass 
!= None): 
 206         if (len(objargs
) > 0): 
 207             if (hasattr(objclass
, "__xmlcdatacontent__")): 
 209                 contentAttr 
= obj
.__xmlcdatacontent
__ 
 210                 obj
.__dict
__[contentAttr
] = str(objargs
[0]) 
 212             return objclass(*objargs
) 
 215     return objutils
.newInstance(objname
, objargs
) 
 218     def __init__(self
, name
, attrs
=None, xsname
=None): 
 226     def getobjtype(self
): 
 227         objtype 
= self
.attrs
.get("objtype") 
 228         if (objtype 
== None): 
 229             if (len(self
.children
) > 0): 
 235 class NsElement(object): 
 239         self
.defaultNS 
= None 
 243         return ((self
.nsMap 
== {}) and (self
.targetNS 
== None) and (self
.defaultNS 
== None)) 
 245     def setKnownTypes(self
, masterKnownTypes
, masterKnownNamespaces
, parentNSE
): 
 246         # if we're a nested element, extend our parent element's mapping 
 247         if parentNSE 
!= None: 
 248             self
.knownTypes 
= parentNSE
.knownTypes
.copy() 
 249             # but if we have a different default namespace, replace the parent's default mappings 
 250             if parentNSE
.defaultNS 
!= self
.defaultNS
: 
 251                 newKT 
= self
.knownTypes
.copy() 
 253                     if tag
.find(':') < 0: 
 254                         del self
.knownTypes
[tag
] 
 255             newMap 
= parentNSE
.nsMap
.copy() 
 257                 for k
, v 
in self
.nsMap
.iteritems(): 
 263         # TODO: instead of starting with the knownNamespaces, start with the "xmlms" mappings 
 264         # for this element. Then we'd only process the namespaces and tags we need to. 
 265         # But for now, this works. 
 266         for long, short 
in masterKnownNamespaces
.iteritems(): 
 267             reversedKNS
[short
] = long 
 268         mapLongs 
= self
.nsMap
.values() 
 269         for tag
, mapClass 
in masterKnownTypes
.iteritems(): 
 271             if i 
>= 0:                                      # e.g. "wsdl:description" 
 272                 knownTagShort 
= tag
[:i
]                     # "wsdl" 
 273                 knownTagName 
= tag
[i
+1:]                    # "description" 
 274                 knownTagLong  
= reversedKNS
[knownTagShort
]  # e.g. "http://schemas.xmlsoap.org/wsdl" 
 275                 if (knownTagLong 
in mapLongs
): 
 276                     for mShort
, mLong 
in self
.nsMap
.iteritems(): 
 277                         if mLong 
== knownTagLong
: 
 278                             actualShort 
= mShort  
# e.g. "ws" 
 279                             actualTag 
= '%s:%s' % (actualShort
, knownTagName
) 
 280                             self
.knownTypes
[actualTag
] = mapClass
 
 282                 if self
.defaultNS 
== knownTagLong
: 
 283                     self
.knownTypes
[knownTagName
] = mapClass
 
 284             else:                                           # e.g. "ItemSearchRequest" 
 285                 self
.knownTypes
[tag
] = mapClass
 
 286 ##                print 'mapping <%s> to class "%s"' % (tag, mapClass.__name__) 
 288     def expandQName(self
, eName
, attrName
, attrValue
): 
 290         i 
= attrValue
.rfind(':') 
 292             if self
.defaultNS 
!= None: 
 293                 bigValue 
= '%s:%s' % (self
.defaultNS
, attrValue
) 
 295             attrNS 
= attrValue
[:i
] 
 296             attrNCName 
= attrValue
[i
+1:] 
 297             for shortNs
, longNs 
in self
.nsMap
.iteritems(): 
 298                 if shortNs 
== attrNS
: 
 299                     bigValue 
= '%s:%s' % (longNs
, attrNCName
) 
 301 ##        print '[expandQName] input attrName = "%s" and attrValue "%s"; output = "%s"' % (attrName, attrValue, bigValue) 
 304 class XMLObjectFactory(xml
.sax
.ContentHandler
): 
 305     def __init__(self
, knownTypes
=None, knownNamespaces
=None): 
 306         self
.rootelement 
= None 
 307         if (knownTypes 
== None): 
 310             self
.knownTypes 
= knownTypes
 
 311         if (knownNamespaces 
== None): 
 312             self
.knownNamespaces 
= {} 
 314             self
.knownNamespaces 
= knownNamespaces
 
 316         self
.elementstack 
= [] 
 318         self
.collectContent 
= None 
 319         xml
.sax
.handler
.ContentHandler
.__init
__(self
) 
 321     def appendElementStack(self
, newElement
, newNS
): 
 322         self
.elementstack
.append(newElement
) 
 323         if (newNS
.isEmpty()): 
 324             if (len(self
.nsstack
) > 0): 
 325                 newNS 
= self
.nsstack
[-1] 
 327                 newNS
.knownTypes 
= self
.knownTypes
.copy() 
 329             if (len(self
.nsstack
) > 0): 
 330                 newNS
.setKnownTypes(self
.knownTypes
, self
.knownNamespaces
, self
.nsstack
[-1]) 
 332                 newNS
.setKnownTypes(self
.knownTypes
, self
.knownNamespaces
, None) 
 333         self
.nsstack
.append(newNS
) 
 336     def popElementStack(self
): 
 337         element 
= self
.elementstack
.pop() 
 338         nse 
= self
.nsstack
.pop() 
 341     ## ContentHandler methods 
 342     def startElement(self
, name
, attrs
): 
 343 ##        print '[startElement] <%s>' % (name) 
 344         if name 
== 'xs:annotation' or name 
== 'xsd:annotation': # should use namespace mapping here 
 346             self
.appendElementStack(Element(name
, attrs
.copy()), NsElement()) 
 349         if self
.collectContent 
!= None: 
 350             strVal 
= '<%s' % (name
) 
 351             for aKey
, aVal 
in attrs
.items(): 
 352                 strVal 
+= (' %s="%s"' % (aKey
, aVal
)) 
 354             self
.collectContent
.content 
+= strVal
 
 356         if name
.find(':') > -1:  # Strip namespace prefixes for now until actually looking them up in xsd 
 357             name 
= name
[name
.rfind(":") + 1:] 
 358         element 
= Element(name
, attrs
.copy(), xsname
=xsname
) 
 359         # if the element has namespace attributes, process them and add them to our stack 
 361         for k 
in attrs
.getNames(): 
 362             if k
.startswith('xmlns'): 
 364                 eLongNs 
= longNs 
+ '/' 
 365                 if str(eLongNs
) in asDict(self
.knownNamespaces
): 
 368                     nse
.defaultNS 
= longNs
 
 371                     nse
.nsMap
[shortNs
] = longNs
 
 372             elif k 
== 'targetNamespace': 
 373                 nse
.targetNS 
= attrs
.getValue(k
) 
 374         nse 
= self
.appendElementStack(element
, nse
) 
 375         element
.objclass 
= nse
.knownTypes
.get(xsname
) 
 376         if (hasattr(element
.objclass
, "__xmlcontent__")): 
 377             self
.collectContent 
= element
 
 379     def characters(self
, content
): 
 380 ##        print '[characters] "%s" (%s)' % (content, type(content)) 
 381         if (content 
!= None): 
 382             if self
.collectContent 
!= None: 
 383                 self
.collectContent
.content 
+= content
 
 385                 self
.elementstack
[-1].content 
+= content
 
 387     def endElement(self
, name
): 
 388 ##        print "[endElement] </%s>" % name 
 390         if name
.find(":") > -1:  # Strip namespace prefixes for now until actually looking them up in xsd 
 391             name 
= name
[name
.find(":") + 1:] 
 393             if xsname 
== "xs:annotation" or xsname 
== "xsd:annotation":     # here too 
 395                 self
.popElementStack() 
 397         if self
.collectContent 
!= None: 
 398             if xsname 
!= self
.collectContent
.xsname
: 
 399                 self
.collectContent
.content 
+= ('</%s>' % (xsname
)) 
 400                 self
.popElementStack() 
 403                 self
.collectContent 
= None 
 404         oldChildren 
= self
.elementstack
[-1].children
 
 405         element
, nse 
= self
.popElementStack() 
 406         if ((len(self
.elementstack
) > 1) and (self
.elementstack
[-1].getobjtype() == "None")): 
 407             parentElement 
= self
.elementstack
[-2] 
 408 ##            print "[endElement] %s: found parent with objtype==None: using its grandparent" % name 
 409         elif (len(self
.elementstack
) > 0): 
 410             parentElement 
= self
.elementstack
[-1] 
 411         objtype 
= element
.getobjtype() 
 412 ##        print "element objtype is: ", objtype 
 413         if (objtype 
== "None"): 
 414 ##            print "[endElement] %s: skipping a (objtype==None) end tag" % name 
 416         constructorarglist 
= [] 
 417         if (len(element
.content
) > 0): 
 418             strippedElementContent 
= element
.content
.strip() 
 419             if (len(strippedElementContent
) > 0): 
 420                 constructorarglist
.append(element
.content
) 
 421         obj 
= _objectfactory(objtype
, constructorarglist
, element
.objclass
) 
 422         complexType 
= getComplexType(obj
) 
 424             if (hasattr(obj
, "__xmlname__") and getattr(obj
, "__xmlname__") == "sequence"): 
 425                 self
.elementstack
[-1].children 
= oldChildren
 
 427         if (len(element
.attrs
) > 0) and not isinstance(obj
, list): 
 428 ##            print "[endElement] %s: element has attrs and the obj is not a list" % name 
 429             for attrname
, attr 
in element
.attrs
.items(): 
 430                 if attrname 
== XMLNS 
or attrname
.startswith(XMLNS_PREFIX
): 
 431                     if attrname
.startswith(XMLNS_PREFIX
): 
 432                         ns 
= attrname
[XMLNS_PREFIX_LENGTH
:] 
 435                     if complexType 
!= None: 
 436                         if not hasattr(obj
, "__xmlnamespaces__"): 
 437                             obj
.__xmlnamespaces
__ = {ns:attr}
 
 438                         elif ns 
not in obj
.__xmlnamespaces
__: 
 439                             if (hasattr(obj
.__class
__, "__xmlnamespaces__")  
 440                                 and (obj
.__xmlnamespaces
__ is obj
.__class
__.__xmlnamespaces
__)): 
 441                                 obj
.__xmlnamespaces
__ = dict(obj
.__xmlnamespaces
__) 
 442                             obj
.__xmlnamespaces
__[ns
] = attr
 
 443                 elif not attrname 
== "objtype": 
 444                     if attrname
.find(":") > -1:  # Strip namespace prefixes for now until actually looking them up in xsd 
 445                         attrname 
= attrname
[attrname
.find(":") + 1:] 
 446                     if (complexType 
!= None): 
 447                         xsdElement 
= complexType
.findElement(attrname
) 
 448                         if (xsdElement 
!= None): 
 449                             type = xsdElement
.type 
 450 ##                            print 'Unmarshalling element "%s", attribute "%s" with type "%s"' % (name, xsdElement.name, type) 
 452                                 if (type == TYPE_QNAME
): 
 453                                     attr 
= nse
.expandQName(name
, attrname
, attr
) 
 454                                 type = xsdToLangType(type) 
 455                                 ### ToDO remove maxOccurs hack after bug 177 is fixed 
 456                                 if attrname 
== "maxOccurs" and attr 
== "unbounded": 
 458                                 attr 
= _objectfactory(type, attr
) 
 460                         setattrignorecase(obj
, _toAttrName(obj
, attrname
), attr
) 
 461                     except AttributeError: 
 462                         errorString 
= 'Error unmarshalling XML document at line %i, column %i: The object type of attribute "%s" of XML element "%s": not specified or known' % (self
._locator
.getLineNumber(), self
._locator
.getColumnNumber(), attrname
, name
) 
 463                         raise UnmarshallerException(errorString
) 
 464 ##                    obj.__dict__[_toAttrName(obj, attrname)] = attr 
 465         # stuff any child attributes meant to be in a sequence via the __xmlflattensequence__ 
 467         if hasattr(obj
, "__xmlflattensequence__"): 
 468             flatten 
= obj
.__xmlflattensequence
__ 
 469             if (isinstance(flatten
, dict)): 
 470                 for sequencename
, xmlnametuple 
in flatten
.items(): 
 471                     if (xmlnametuple 
== None): 
 472                         flattenDict
[sequencename
] = sequencename
 
 473                     elif (not isinstance(xmlnametuple
, (tuple, list))): 
 474                         flattenDict
[str(xmlnametuple
)] = sequencename
 
 476                         for xmlname 
in xmlnametuple
: 
 477 ##                            print "[endElement]: adding flattenDict[%s] = %s" % (xmlname, sequencename) 
 478                             flattenDict
[xmlname
] = sequencename
 
 480                 raise Exception("Invalid type for __xmlflattensequence___ : it must be a dict") 
 482         # reattach an object"s attributes to it 
 483         for childname
, child 
in element
.children
: 
 484 ##            print "[endElement] childname is: ", childname, "; child is: ", child 
 485             if (childname 
in flattenDict
): 
 486                 sequencename 
= _toAttrName(obj
, flattenDict
[childname
]) 
 487                 if (not hasattr(obj
, sequencename
)): 
 488                     obj
.__dict
__[sequencename
] = [] 
 489                 sequencevalue 
= getattr(obj
, sequencename
) 
 490                 if (sequencevalue 
== None): 
 491                     obj
.__dict
__[sequencename
] = [] 
 492                     sequencevalue 
= getattr(obj
, sequencename
) 
 493                 sequencevalue
.append(child
) 
 494             elif (objtype 
== "list"): 
 496             elif isinstance(obj
, dict): 
 497                 if (childname 
== DICT_ITEM_NAME
): 
 498                     obj
[child
[DICT_ITEM_KEY_NAME
]] = child
[DICT_ITEM_VALUE_NAME
] 
 500                     obj
[childname
] = child
 
 502 ##                print "childname = %s, obj = %s, child = %s" % (childname, repr(obj), repr(child)) 
 504                     setattrignorecase(obj
, _toAttrName(obj
, childname
), child
) 
 505                 except AttributeError: 
 506                     raise MarshallerException("Error unmarshalling child element \"%s\" of XML element \"%s\": object type not specified or known" % (childname
, name
)) 
 507 ##                obj.__dict__[_toAttrName(obj, childname)] = child 
 509         if (complexType 
!= None): 
 510             for element 
in complexType
.elements
: 
 512                     elementName 
= _toAttrName(obj
, element
.name
) 
 513                     if ((elementName 
not in obj
.__dict
__) or (obj
.__dict
__[elementName
] == None)): 
 514                         langType 
= xsdToLangType(element
.type) 
 515                         defaultValue 
= _objectfactory(langType
, element
.default
) 
 516                         obj
.__dict
__[elementName
] = defaultValue
 
 519         if (isinstance(obj
, list)): 
 520             if ((element
.attrs
.has_key("mutable")) and (element
.attrs
.getValue("mutable") == "false")): 
 524         if (len(self
.elementstack
) > 0): 
 525 ##            print "[endElement] appending child with name: ", name, "; objtype: ", objtype 
 526             parentElement
.children
.append((name
, obj
)) 
 527 ##            print "parentElement now has ", len(parentElement.children), " children" 
 529             self
.rootelement 
= obj
 
 531     def getRootObject(self
): 
 532         return self
.rootelement
 
 534 def _toAttrName(obj
, name
): 
 535     if (hasattr(obj
, "__xmlrename__")): 
 536         for key
, val 
in obj
.__xmlrename
__.iteritems(): 
 540 ##    if (name.startswith("__") and not name.endswith("__")): 
 541 ##        name = "_%s%s" % (obj.__class__.__name__, name) 
 544 __typeMappingXsdToLang 
= { 
 548     "date": "str", # ToDO Need to work out how to create lang date types 
 550     "decimal": "float", # ToDO Does python have a better fixed point type? 
 557     "unicode": "unicode", 
 559     "duration": "str", # see above (date) 
 560     "datetime": "str", # see above (date) 
 561     "time": "str", # see above (date) 
 564     "blob" : "str",         # ag:blob 
 565     "currency" : "str",     # ag:currency 
 568 def xsdToLangType(xsdType
): 
 569     if xsdType
.startswith(XMLSCHEMA_XSD_URL
): 
 570         xsdType 
= xsdType
[len(XMLSCHEMA_XSD_URL
)+1:] 
 571     elif xsdType
.startswith(AG_URL
): 
 572         xsdType 
= xsdType
[len(AG_URL
)+1:]         
 573     langType 
= __typeMappingXsdToLang
.get(xsdType
) 
 574     if (langType 
== None): 
 575         raise Exception("Unknown xsd type %s" % xsdType
) 
 578 def langToXsdType(langType
): 
 579     if langType 
in asDict(__typeMappingXsdToLang
): 
 580         return '%s:%s' % (XMLSCHEMA_XSD_URL
, langType
) 
 583 def _getXmlValue(langValue
): 
 584     if (isinstance(langValue
, bool)): 
 585         return str(langValue
).lower() 
 586     elif (isinstance(langValue
, unicode)): 
 587         return langValue
.encode() 
 589         return str(langValue
) 
 591 def unmarshal(xmlstr
, knownTypes
=None, knownNamespaces
=None, xmlSource
=None): 
 592     objectfactory 
= XMLObjectFactory(knownTypes
, knownNamespaces
) 
 594         xml
.sax
.parseString(xmlstr
, objectfactory
) 
 595     except xml
.sax
.SAXParseException
, errorData
: 
 596         if xmlSource 
== None: 
 597             xmlSource 
= 'unknown' 
 598         errorString 
= 'SAXParseException ("%s") detected at line %d, column %d in XML document from source "%s" ' % (errorData
.getMessage(), errorData
.getLineNumber(), errorData
.getColumnNumber(), xmlSource
) 
 599         raise UnmarshallerException(errorString
) 
 600     return objectfactory
.getRootObject() 
 602 def marshal(obj
, elementName
=None, prettyPrint
=False, marshalType
=True, indent
=0, knownTypes
=None, knownNamespaces
=None, encoding
=-1): 
 603 ##    print '[marshal] entered with elementName = "%s"' % (elementName) 
 604     worker 
= XMLMarshalWorker(prettyPrint
=prettyPrint
, marshalType
=marshalType
, knownTypes
=knownTypes
, knownNamespaces
=knownNamespaces
)     
 605     if obj 
!= None and hasattr(obj
, '__xmldeepexclude__'): 
 606         worker
.xmldeepexclude 
= obj
.__xmldeepexclude
__ 
 607     xmlstr 
= "".join(worker
._marshal
(obj
, elementName
, indent
=indent
)) 
 608     if (isinstance(encoding
, basestring
)): 
 609         return '<?xml version="1.0" encoding="%s"?>\n%s' % (encoding
, xmlstr
.encode(encoding
)) 
 610     elif (encoding 
== None): 
 613         return '<?xml version="1.0" encoding="%s"?>\n%s' % (sys
.getdefaultencoding(), xmlstr
) 
 615 class XMLMarshalWorker(object): 
 616     def __init__(self
, marshalType
=True, prettyPrint
=False, knownTypes
=None, knownNamespaces
=None): 
 617         if knownTypes 
== None: 
 620             self
.knownTypes 
= knownTypes
 
 621         if knownNamespaces 
== None: 
 622             self
.knownNamespaces 
= {} 
 624             self
.knownNamespaces 
= knownNamespaces
 
 625         self
.prettyPrint 
= prettyPrint
 
 626         self
.marshalType 
= marshalType
 
 627         self
.xmldeepexclude 
= [] 
 630     def getNSPrefix(self
): 
 631         if len(self
.nsstack
) > 0: 
 632             return self
.nsstack
[-1].prefix
 
 635     def isKnownType(self
, elementName
): 
 637         nse 
= self
.nsstack
[-1] 
 638         i 
= elementName
.rfind(':') 
 640             prefix 
= elementName
[:i
] 
 641             name 
= elementName
[i
+1:] 
 643             prefix 
= DEFAULT_NAMESPACE_KEY
 
 645         for shortNs
, longNs 
in nse
.nameSpaces
.iteritems(): 
 646             if shortNs 
== prefix
: 
 649         if tagLongNs 
== None: 
 650             knownTagName 
= elementName
 
 652             knownShortNs 
= self
.knownNamespaces
[tagLongNs
] 
 653             knownTagName 
= knownShortNs 
+ ':' + name
 
 654         if (knownTagName 
in asDict(self
.knownTypes
)): 
 655             knownClass 
= self
.knownTypes
[knownTagName
] 
 659     def popNSStack(self
): 
 662     def appendNSStack(self
, obj
): 
 665         for nse 
in self
.nsstack
: 
 666             for k
, v 
in nse
.nsMap
.iteritems(): 
 668                 if k 
== DEFAULT_NAMESPACE_KEY
: 
 672         if hasattr(obj
, "__xmlnamespaces__"): 
 673             ns 
= getattr(obj
, "__xmlnamespaces__") 
 676             for nameSpaceKey 
in keys
: 
 677                 nameSpaceUrl 
= ns
[nameSpaceKey
] 
 678                 if nameSpaceUrl 
in nameSpaces
.values(): 
 679                     for k
, v 
in nameSpaces
.iteritems(): 
 680                         if v 
== nameSpaceUrl
: 
 684                     if nameSpaceKey 
== "": 
 685                         defaultLongNS 
= nameSpaceUrl
 
 686                         nameSpaces
[DEFAULT_NAMESPACE_KEY
] = nameSpaceUrl
 
 687                         newNS
.nsMap
[DEFAULT_NAMESPACE_KEY
] = nameSpaceUrl
 
 688                         nameSpaceAttrs 
+= ' xmlns="%s" ' % (nameSpaceUrl
) 
 690                         nameSpaces
[nameSpaceKey
] = nameSpaceUrl
 
 691                         newNS
.nsMap
[nameSpaceKey
] = nameSpaceUrl
 
 692                         nameSpaceAttrs 
+= ' xmlns:%s="%s" ' % (nameSpaceKey
, nameSpaceUrl
) 
 693             nameSpaceAttrs 
= nameSpaceAttrs
.rstrip() 
 694         if len(self
.nsstack
) > 0: 
 695             newNS
.prefix 
= self
.nsstack
[-1].prefix
 
 698         if hasattr(obj
, "__xmldefaultnamespace__"): 
 699             longPrefixNS 
= getattr(obj
, "__xmldefaultnamespace__") 
 700             if longPrefixNS 
== defaultLongNS
: 
 704                     for k
, v 
in nameSpaces
.iteritems(): 
 705                         if v 
== longPrefixNS
: 
 706                             newNS
.prefix 
= k 
+ ':' 
 708 ##                    print '[appendNSStack] found longPrefixNS in nameSpaces = "%s"' % (newNS.prefix) 
 710                     if (longPrefixNS 
in asDict(self
.knownNamespaces
)): 
 711                         newNS
.prefix 
= self
.knownNamespaces
[longPrefixNS
] + ':' 
 713                         raise MarshallerException('Error marshalling __xmldefaultnamespace__ ("%s") not defined in namespace stack' % (longPrefixNS
)) 
 714         if hasattr(obj
, "targetNamespace"): 
 715             newNS
.targetNS 
= obj
.targetNamespace
 
 716         elif len(self
.nsstack
) > 0: 
 717             newNS
.targetNS 
= self
.nsstack
[-1].targetNS
 
 718         newNS
.nameSpaces 
= nameSpaces
 
 719         self
.nsstack
.append(newNS
) 
 720         return nameSpaceAttrs       
 
 722     def contractQName(self
, value
, obj
, attr
): 
 723         value 
= langToXsdType(value
) 
 728             # the value doesn't have a namespace and we couldn't map it to an XSD type...what to do? 
 729             # (a) just write it, as is, and hope it's in the default namespace (for now) 
 730             # (b) throw an exception so we can track down the bad code (later) 
 732         if (longNS 
in self
.nsstack
[-1].nameSpaces
.values()): 
 733             for kShort
, vLong 
in self
.nsstack
[-1].nameSpaces
.iteritems(): 
 738             shortNS 
= longNS    
# if we can't find the long->short mappping, just use longNS 
 739         if shortNS 
== DEFAULT_NAMESPACE_KEY
: 
 742             value 
= shortNS 
+ ':' + value
[i
+1:] 
 745     def _genObjTypeStr(self
, typeString
): 
 747             return ' objtype="%s"' % typeString
 
 750     def _marshal(self
, obj
, elementName
=None, nameSpacePrefix
="", indent
=0): 
 752             xmlMarshallerLogger
.debug("--> _marshal: elementName=%s%s, type=%s, obj=%s, indent=%d" % (nameSpacePrefix
, elementName
, type(obj
), str(obj
), indent
)) 
 754             xmlMarshallerLogger
.debug("--> _marshal: elementName=%s%s, obj is None, indent=%d" % (nameSpacePrefix
, elementName
, indent
)) 
 756         excludeAttrs
.extend(self
.xmldeepexclude
) 
 757         if hasattr(obj
, "__xmlexclude__"): 
 758             excludeAttrs
.extend(obj
.__xmlexclude
__) 
 759         prettyPrint 
= self
.prettyPrint
 
 760         knownTypes 
= self
.knownTypes
 
 762         if self
.prettyPrint 
or indent
: 
 770         ## Determine the XML element name. If it isn"t specified in the 
 771         ## parameter list, look for it in the __xmlname__ Lang 
 772         ## attribute, else use the default generic BASETYPE_ELEMENT_NAME. 
 773         nameSpaceAttrs 
= self
.appendNSStack(obj
) 
 774         nameSpacePrefix 
= self
.getNSPrefix()        
 776             if hasattr(obj
, "__xmlname__"): 
 777                 elementName 
= nameSpacePrefix 
+ obj
.__xmlname
__ 
 779                 elementName 
= nameSpacePrefix 
+ BASETYPE_ELEMENT_NAME
 
 781             elementName 
= nameSpacePrefix 
+ elementName
 
 782 ##        print '[XMLMarshalWorker._marshal] elementName "%s"; nameSpaceAttrs is "%s"' % (elementName, nameSpaceAttrs) 
 784         if (hasattr(obj
, "__xmlsequencer__")) and (obj
.__xmlsequencer
__ != None): 
 785             if (XMLSCHEMA_XSD_URL 
in self
.nsstack
[-1].nameSpaces
.values()): 
 786                 for kShort
, vLong 
in self
.nsstack
[-1].nameSpaces
.iteritems(): 
 787                     if vLong 
== XMLSCHEMA_XSD_URL
: 
 788                         xsdPrefix 
= kShort 
+ ':' 
 792             elementAdd 
= xsdPrefix 
+ obj
.__xmlsequencer
__ 
 796     ##    print "marshal: entered with elementName: ", elementName 
 798         ## Add more members_to_skip based on ones the user has selected 
 799         ## via the __xmlexclude__ and __xmldeepexclude__ attributes. 
 800         members_to_skip
.extend(excludeAttrs
) 
 801         # Marshal the attributes that are selected to be XML attributes. 
 803         className 
= ag_className(obj
) 
 804         classNamePrefix 
= "_" + className
 
 805         if hasattr(obj
, "__xmlattributes__"): 
 806             xmlattributes 
= obj
.__xmlattributes
__ 
 807             members_to_skip
.extend(xmlattributes
) 
 808             for attr 
in xmlattributes
: 
 809 ##                print 'Processing element "%s"; attribute "%s"' % (elementName, attr) 
 810                 internalAttrName 
= attr
 
 812                 if (attr
.startswith("__") and not attr
.endswith("__")):  
 813                     internalAttrName 
= classNamePrefix 
+ attr
 
 815                 # Fail silently if a python attribute is specified to be 
 816                 # an XML attribute but is missing. 
 817 ##            print "marshal:   processing attribute ", internalAttrName 
 818                 attrNameSpacePrefix 
= "" 
 819                 if hasattr(obj
, "__xmlattrnamespaces__"): 
 820                     for nameSpaceKey
, nameSpaceAttributes 
in getattr(obj
, "__xmlattrnamespaces__").iteritems(): 
 821                         if nameSpaceKey 
== nameSpacePrefix
[:-1]: # Don't need to specify attribute namespace if it is the same as its element 
 823                         if attr 
in nameSpaceAttributes
: 
 824                             attrNameSpacePrefix 
= nameSpaceKey 
+ ":" 
 827                 value 
= attrs
.get(internalAttrName
) 
 828                 if (hasattr(obj
, "__xmlrename__") and attr 
in asDict(obj
.__xmlrename
__)): 
 829                     attr 
= obj
.__xmlrename
__[attr
] 
 831                 complexType 
= getComplexType(obj
) 
 832                 if (complexType 
!= None): 
 833                     xsdElement 
= complexType
.findElement(attr
) 
 834                 if (xsdElement 
!= None): 
 835                     default 
= xsdElement
.default
 
 836                     if (default 
!= None): 
 837                         if ((default 
== value
) or (default 
== _getXmlValue(value
))): 
 842                         elif xsdElement
.type == TYPE_QNAME
: 
 843                             value 
= self
.contractQName(value
, obj
, attr
) 
 847                 # ToDO remove maxOccurs hack after bug 177 is fixed 
 848                 if attr 
== "maxOccurs" and value 
== -1: 
 851                 if isinstance(value
, bool): 
 857                     value 
= objutils
.toDiffableRepr(value
) 
 859                 objattrs 
+= ' %s%s="%s"' % (attrNameSpacePrefix
, attr
, saxutils
.escape(value
)) 
 860     ##            print "marshal:   new objattrs is: ", objattrs 
 863         elif isinstance(obj
, bool): 
 864             objTypeStr 
= self
._genObjTypeStr
("bool") 
 865             xmlString 
= ['%s<%s%s>%s</%s>%s' % (prefix
, elementName
, objTypeStr
, obj
, elementName
, newline
)] 
 866         elif isinstance(obj
, int): 
 867             objTypeStr 
= self
._genObjTypeStr
("int") 
 868             xmlString 
= ['%s<%s%s>%s</%s>%s' % (prefix
, elementName
, objTypeStr
, str(obj
), elementName
, newline
)] 
 869         elif isinstance(obj
, long): 
 870             objTypeStr 
= self
._genObjTypeStr
("long") 
 871             xmlString 
= ['%s<%s%s>%s</%s>%s' % (prefix
, elementName
, objTypeStr
, str(obj
), elementName
, newline
)] 
 872         elif isinstance(obj
, float): 
 873             objTypeStr 
= self
._genObjTypeStr
("float") 
 874             xmlString 
= ['%s<%s%s>%s</%s>%s' % (prefix
, elementName
, objTypeStr
, str(obj
), elementName
, newline
)] 
 875         elif isinstance(obj
, unicode): # have to check before basestring - unicode is instance of base string 
 876             xmlString 
= ['%s<%s>%s</%s>%s' % (prefix
, elementName
, saxutils
.escape(obj
.encode()), elementName
, newline
)] 
 877         elif isinstance(obj
, basestring
): 
 878             xmlString 
= ['%s<%s>%s</%s>%s' % (prefix
, elementName
, saxutils
.escape(obj
), elementName
, newline
)] 
 879         elif isinstance(obj
, list): 
 883                 objTypeStr 
= self
._genObjTypeStr
("list") 
 884                 xmlString 
= ['%s<%s%s>%s' % (prefix
, elementName
, objTypeStr
, newline
)] 
 886                     xmlString
.extend(self
._marshal
(item
, indent
=indent
+increment
)) 
 887                 xmlString
.append("%s</%s>%s" % (prefix
, elementName
, newline
)) 
 888         elif isinstance(obj
, tuple): 
 892                 objTypeStr 
= self
._genObjTypeStr
("list") 
 893                 xmlString 
= ['%s<%s%s mutable="false">%s' % (prefix
, elementName
, objTypeStr
, newline
)] 
 895                     xmlString
.extend(self
._marshal
(item
, indent
=indent
+increment
)) 
 896                 xmlString
.append("%s</%s>%s" % (prefix
, elementName
, newline
)) 
 897         elif isinstance(obj
, dict): 
 898             objTypeStr 
= self
._genObjTypeStr
("dict") 
 899             xmlString 
= ['%s<%s%s>%s' % (prefix
, elementName
, objTypeStr
, newline
)] 
 900             subprefix 
= prefix 
+ " "*increment
 
 901             subindent 
= indent 
+ 2*increment
 
 905                 xmlString
.append("%s<%s>%s" % (subprefix
, DICT_ITEM_NAME
, newline
)) 
 906                 xmlString
.extend(self
._marshal
(key
, elementName
=DICT_ITEM_KEY_NAME
, indent
=subindent
)) 
 907                 xmlString
.extend(self
._marshal
(obj
[key
], elementName
=DICT_ITEM_VALUE_NAME
, indent
=subindent
)) 
 908                 xmlString
.append("%s</%s>%s" % (subprefix
, DICT_ITEM_NAME
, newline
)) 
 909             xmlString
.append("%s</%s>%s" % (prefix
, elementName
, newline
)) 
 910         elif hasattr(obj
, "__xmlcontent__"): 
 911             contentValue 
= getattr(obj
, obj
.__xmlcontent
__) 
 912             if contentValue 
== None:  
 915                 contentValue 
= saxutils
.escape(contentValue
) 
 916             xmlString 
= ["%s<%s%s%s>%s</%s>%s" % (prefix
, elementName
, nameSpaceAttrs
, objattrs
, contentValue
, elementName
, newline
)]         
 918             # Only add the objtype if the element tag is unknown to us. 
 919             if (self
.isKnownType(elementName
) == True):  
 922                 objTypeStr 
= self
._genObjTypeStr
("%s.%s" % (obj
.__class
__.__module
__, className
)) 
 923             xmlString 
= ['%s<%s%s%s%s' % (prefix
, elementName
, nameSpaceAttrs
, objattrs
, objTypeStr
)] 
 924             # get the member, value pairs for the object, filtering out the types we don"t support 
 925             if (elementAdd 
!= None): 
 926                 prefix 
+= increment
*" " 
 929             if hasattr(obj
, "__xmlbody__"): 
 930                 xmlbody 
= getattr(obj
, obj
.__xmlbody
__) 
 932                     xmlMemberString
.append(xmlbody
)            
 934                 if hasattr(obj
, "__xmlattrgroups__"): 
 935                     attrGroups 
= obj
.__xmlattrgroups
__.copy() 
 936                     if (not isinstance(attrGroups
, dict)): 
 937                         raise "__xmlattrgroups__ is not a dict, but must be" 
 938                     for n 
in attrGroups
.iterkeys(): 
 939                         members_to_skip
.extend(attrGroups
[n
]) 
 942                 # add the list of all attributes to attrGroups 
 943                 eList 
= obj
.__dict
__.keys()     
 945                 attrGroups
["__nogroup__"] = eList
 
 947                 for eName
, eList 
in attrGroups
.iteritems(): 
 948                     if (eName 
!= "__nogroup__"): 
 949                         prefix 
+= increment
*" " 
 951                         objTypeStr 
= self
._genObjTypeStr
("None") 
 952                         xmlMemberString
.append('%s<%s%s>%s' % (prefix
, eName
, objTypeStr
, newline
)) 
 954                         value 
= obj
.__dict
__[name
] 
 955                         if eName 
== "__nogroup__" and name 
in members_to_skip
: continue 
 956                         if name
.startswith("__") and name
.endswith("__"): continue 
 957                         if (hasattr(obj
, "__xmlcdatacontent__") and (obj
.__xmlcdatacontent
__ == name
)): 
 959                         subElementNameSpacePrefix 
= nameSpacePrefix
 
 960                         if hasattr(obj
, "__xmlattrnamespaces__"): 
 961                             for nameSpaceKey
, nameSpaceValues 
in getattr(obj
, "__xmlattrnamespaces__").iteritems(): 
 962                                 if name 
in nameSpaceValues
: 
 963                                     subElementNameSpacePrefix 
= nameSpaceKey 
+ ":" 
 965                         # handle sequences listed in __xmlflattensequence__ 
 966                         # specially: instead of listing the contained items inside 
 967                         # of a separate list, as God intended, list them inside 
 968                         # the object containing the sequence. 
 969                         if (hasattr(obj
, "__xmlflattensequence__") and (value 
!= None) and (name 
in asDict(obj
.__xmlflattensequence
__))): 
 970                             xmlnametuple 
= obj
.__xmlflattensequence
__[name
] 
 971                             if (xmlnametuple 
== None): 
 972                                 xmlnametuple 
= [name
] 
 973                             elif (not isinstance(xmlnametuple
, (tuple,list))): 
 974                                 xmlnametuple 
= [str(xmlnametuple
)] 
 976                             if (len(xmlnametuple
) == 1): 
 977                                 xmlname 
= xmlnametuple
[0] 
 979                             if not isinstance(value
, (list, tuple)): 
 981                             for seqitem 
in value
: 
 982     ##                            xmlname = xmlnametuple[ix] 
 984     ##                            if (ix >= len(xmlnametuple)): 
 986                                 xmlMemberString
.extend(self
._marshal
(seqitem
, xmlname
, subElementNameSpacePrefix
, indent
=indent
+increment
)) 
 988                             if (hasattr(obj
, "__xmlrename__") and name 
in asDict(obj
.__xmlrename
__)): 
 989                                 xmlname 
= obj
.__xmlrename
__[name
] 
 992                             xmlMemberString
.extend(self
._marshal
(value
, xmlname
, subElementNameSpacePrefix
, indent
=indent
+increment
)) 
 993                     if (eName 
!= "__nogroup__"): 
 994                         xmlMemberString
.append("%s</%s>%s" % (prefix
, eName
, newline
)) 
 995                         prefix 
= prefix
[:-increment
] 
 998             # if we have nested elements, add them here, otherwise close the element tag immediately. 
1000             for s 
in xmlMemberString
: 
1001                 if (len(s
) > 0): newList
.append(s
) 
1002             xmlMemberString 
= newList
 
1003             if len(xmlMemberString
) > 0: 
1004                 xmlString
.append(">") 
1005                 if hasattr(obj
, "__xmlbody__"): 
1006                     xmlString
.extend(xmlMemberString
) 
1007                     xmlString
.append("</%s>%s" % (elementName
, newline
)) 
1009                     xmlString
.append(newline
) 
1010                     if (elementAdd 
!= None): 
1011                         xmlString
.append("%s<%s>%s" % (prefix
, elementAdd
, newline
)) 
1012                     xmlString
.extend(xmlMemberString
) 
1013                     if (elementAdd 
!= None): 
1014                         xmlString
.append("%s</%s>%s" % (prefix
, elementAdd
, newline
)) 
1015                         prefix 
= prefix
[:-increment
] 
1017                     xmlString
.append("%s</%s>%s" % (prefix
, elementName
, newline
)) 
1019                 if hasattr(obj
, "__xmlcdatacontent__"): 
1020                     cdataAttr 
= obj
.__xmlcdatacontent
__ 
1021                     cdataContent 
= obj
.__dict
__[cdataAttr
] 
1022                     xmlString
.append("><![CDATA[%s]]></%s>%s" % (cdataContent
, elementName
, newline
)) 
1024                     xmlString
.append("/>%s" % newline
) 
1026         xmlMarshallerLogger
.debug("<-- _marshal: %s" % str(xmlString
)) 
1027         #print "<-- _marshal: %s" % str(xmlString) 
1031 # A simple test, to be executed when the xmlmarshaller is run standalone 
1032 class MarshallerPerson
: 
1033     __xmlname__ 
= "person" 
1034     __xmlexclude__ 
= ["fabulousness",] 
1035     __xmlattributes__ 
= ("nonSmoker",) 
1036     __xmlrename__ 
= {"_phoneNumber": "telephone"}
 
1037     __xmlflattensequence__ 
= {"favoriteWords": ("vocabulary",)}
 
1038     __xmlattrgroups__ 
= {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]}
 
1040     def setPerson(self
): 
1041         self
.firstName 
= "Albert" 
1042         self
.lastName 
= "Camus" 
1043         self
.addressLine1 
= "23 Absurd St." 
1047         self
._phoneNumber 
= "808-303-2323" 
1048         self
.favoriteWords 
= ["angst", "ennui", "existence"] 
1049         self
.phobias 
= ["war", "tuberculosis", "cars"] 
1051         self
.fabulousness 
= "tres tres" 
1052         self
.nonSmoker 
= False 
1054 if isMain(__name__
): 
1055     p1 
= MarshallerPerson() 
1057     xmlP1 
= marshal(p1
, prettyPrint
=True, encoding
="utf-8")         
1058     print "\n########################" 
1059     print   "# testPerson test case #" 
1060     print   "########################" 
1062     p2 
= unmarshal(xmlP1
) 
1063     xmlP2 
= marshal(p2
, prettyPrint
=True, encoding
="utf-8") 
1065         print "Success: repeated marshalling yields identical results" 
1067         print "Failure: repeated marshalling yields different results"