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"