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"