1 #----------------------------------------------------------------------------
2 # Name: xmlmarshaller.py
5 # Authors: John Spurling, Joel Hare, Jeff Norton, 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
19 import xml
.sax
.saxutils
22 import activegrid
.util
.utillang
as utillang
23 import activegrid
.util
.objutils
as objutils
24 import activegrid
.util
.sysutils
as sysutils
25 import activegrid
.util
.aglogging
as aglogging
27 MODULE_PATH
= "__main__"
29 ## ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed
30 ##unboundedVal = 2147483647 # value used for maxOccurs == "unbounded"
33 Special attributes that we recognize:
37 description: the name of the xml element for the marshalled object
39 name: __xmlattributes__
41 description: the name(s) of the Lang string attribute(s) to be
42 marshalled as xml attributes instead of nested xml elements. currently
43 these can only be strings since there"s not a way to get the type
44 information back when unmarshalling.
48 description: the name(s) of the lang attribute(s) to skip when
53 description: describes an alternate Lang <-> XML name mapping.
54 Normally the name mapping is the identity function. __xmlrename__
55 overrides that. The keys are the Lang names, the values are their
58 name: __xmlflattensequence__
59 type: dict, tuple, or list
60 description: the name(s) of the Lang sequence attribute(s) whose
61 items are to be marshalled as a series of xml elements (with an
62 optional keyword argument that specifies the element name to use) as
63 opposed to containing them in a separate sequence element, e.g.:
66 <!-- normal way of marshalling -->
68 <item objtype="int">1</item>
69 <item objtype="int">2</item>
71 <!-- with __xmlflattensequence__ set to {"myseq": "squish"} -->
72 <squish objtype="int">1</squish>
73 <squish objtype="int">2</squish>
75 name: __xmlnamespaces__
77 description: a dict of the namespaces that the object uses. Each item
78 in the dict should consist of a prefix,url combination where the key is
79 the prefix and url is the value, e.g.:
81 __xmlnamespaces__ = { "xsd":"http://www.w3c.org/foo.xsd" }
83 name: __xmldefaultnamespace__
85 description: the prefix of a namespace defined in __xmlnamespaces__ that
86 should be used as the default namespace for the object.
88 name: __xmlattrnamespaces__
90 description: a dict assigning the Lang object"s attributes to the namespaces
91 defined in __xmlnamespaces__. Each item in the dict should consist of a
92 prefix,attributeList combination where the key is the prefix and the value is
93 a list of the Lang attribute names. e.g.:
95 __xmlattrnamespaces__ = { "ag":["firstName", "lastName", "addressLine1", "city"] }
97 name: __xmlattrgroups__
99 description: a dict specifying groups of attributes to be wrapped in an enclosing tag.
100 The key is the name of the enclosing tag; the value is a list of attributes to include
103 __xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]}
105 name: __xmlcdatacontent__
107 description: value is the name of a string attribute that should be assigned CDATA content from the
108 source document and that should be marshalled as CDATA.
110 __xmlcdatacontent__ = "messyContent"
114 global xmlMarshallerLogger
115 xmlMarshallerLogger
= logging
.getLogger("activegrid.util.xmlmarshaller.marshal")
116 # INFO : low-level info
117 # DEBUG : debugging info
119 ################################################################################
123 ################################################################################
125 class Error(Exception):
126 """Base class for errors in this module."""
129 class UnhandledTypeException(Error
):
130 """Exception raised when attempting to marshal an unsupported
133 def __init__(self
, typename
):
134 self
.typename
= typename
136 return "%s is not supported for marshalling." % str(self
.typename
)
138 class XMLAttributeIsNotStringType(Error
):
139 """Exception raised when an object"s attribute is specified to be
140 marshalled as an XML attribute of the enclosing object instead of
143 def __init__(self
, attrname
, typename
):
144 self
.attrname
= attrname
145 self
.typename
= typename
147 return """%s was set to be marshalled as an XML attribute
148 instead of a nested element, but the object"s type is %s, not
149 string.""" % (self
.attrname
, self
.typename
)
151 class MarshallerException(Exception):
154 class UnmarshallerException(Exception):
157 ################################################################################
161 ################################################################################
164 XMLNS_PREFIX
= XMLNS
+ ":"
165 XMLNS_PREFIX_LENGTH
= len(XMLNS_PREFIX
)
166 DEFAULT_NAMESPACE_KEY
= "__DEFAULTNS__"
168 XMLSCHEMA_XSD_URL
= "http://www.w3.org/2001/XMLSchema"
169 AG_URL
= "http://www.activegrid.com/ag.xsd"
171 BASETYPE_ELEMENT_NAME
= "item"
172 DICT_ITEM_NAME
= "qqDictItem"
173 DICT_ITEM_KEY_NAME
= "key"
174 DICT_ITEM_VALUE_NAME
= "value"
176 # This list doesn"t seem to be used.
177 # Internal documentation or useless? You make the call!
178 ##MEMBERS_TO_SKIP = ("__module__", "__doc__", "__xmlname__", "__xmlattributes__",
179 ## "__xmlexclude__", "__xmlflattensequence__", "__xmlnamespaces__",
180 ## "__xmldefaultnamespace__", "__xmlattrnamespaces__",
181 ## "__xmlattrgroups__")
183 ################################################################################
185 # classes and functions
187 ################################################################################
189 def setattrignorecase(object, name
, value
):
190 ## print "[setattrignorecase] name = %s, value = %s" % (name, value)
191 if (name
not in object.__dict
__):
192 namelow
= name
.lower()
193 for attr
in object.__dict
__:
194 if attr
.lower() == namelow
:
195 object.__dict
__[attr
] = value
197 object.__dict
__[name
] = value
199 def getComplexType(obj
):
200 if (hasattr(obj
, "_instancexsdcomplextype")):
201 return obj
._instancexsdcomplextype
202 if (hasattr(obj
, "__xsdcomplextype__")):
203 return obj
.__xsdcomplextype
__
206 def _objectfactory(objtype
, objargs
=None, objclass
=None):
207 "dynamically create an object based on the objtype and return it."
208 if not isinstance(objargs
, list):
210 if (objclass
!= None):
212 if (len(objargs
) > 0):
213 if (hasattr(objclass
, "__xmlcdatacontent__")):
215 contentAttr
= obj
.__xmlcdatacontent
__
216 obj
.__dict
__[contentAttr
] = str(objargs
[0])
218 obj
= objclass(*objargs
)
221 if ((obj
!= None) and (hasattr(obj
, 'postUnmarshal'))):
224 return objutils
.newInstance(objtype
, objargs
)
226 class GenericXMLObject(object):
227 def __init__(self
, content
=None):
229 self
._content
= content
230 self
.__xmlcontent
__ = '_content'
233 return "GenericXMLObject(%s)" % objutils
.toDiffableString(self
.__dict
__)
235 def setXMLAttributes(self
, xmlName
, attrs
=None, children
=None, nsMap
=None, defaultNS
=None):
237 i
= xmlName
.rfind(':')
239 self
.__xmlname
__ = xmlName
240 if defaultNS
!= None:
241 self
.__xmldefaultnamespace
__ = str(defaultNS
)
243 self
.__xmlname
__ = xmlName
[i
+1:]
245 if nsMap
.has_key(prefix
):
246 self
.__xmldefaultnamespace
__ = str(nsMap
[prefix
])
248 for attrname
, attr
in attrs
.items():
249 attrname
= str(attrname
)
250 if attrname
== XMLNS
or attrname
.startswith(XMLNS_PREFIX
):
252 elif attrname
== "objtype":
255 if not hasattr(self
, '__xmlattributes__'):
256 self
.__xmlattributes
__ = []
257 i
= attrname
.rfind(':')
259 prefix
= attrname
[:i
]
260 attrname
= attrname
[i
+1:]
261 if not hasattr(self
, '__xmlattrnamespaces__'):
262 self
.__xmlattrnamespaces
__ = {}
263 if self
.__xmlattrnamespaces
__.has_key(prefix
):
264 alist
= self
.__xmlattrnamespaces
__[prefix
]
267 alist
.append(attrname
)
268 self
.__xmlattrnamespaces
__[prefix
] = alist
269 self
.__xmlattributes
__.append(attrname
)
270 if hasattr(self
, '__xmlattributes__'):
271 self
.__xmlattributes
__.sort()
272 if children
!= None and len(children
) > 0:
275 for childname
, child
in children
:
276 childstr
= str(childname
)
277 if childstr
in childList
:
278 if not flattenList
.has_key(childstr
):
279 flattenList
[childstr
] = (childstr
,)
281 childList
.append(childstr
)
282 if len(flattenList
) > 0:
283 self
.__xmlflattensequence
__ = flattenList
285 def initialize(self
, arg1
=None):
290 def __init__(self
, name
, attrs
=None, xsname
=None):
299 def getobjtype(self
):
300 # objtype = self.attrs.get("objtype")
301 objtype
= self
.objtype
302 if (objtype
== None):
303 if (len(self
.children
) > 0):
309 class NsElement(object):
313 self
.defaultNS
= None
317 if self
.prefix
== None:
318 strVal
= 'prefix = None; '
320 strVal
= 'prefix = "%s"; ' % (self
.prefix
)
321 if self
.targetNS
== None:
322 strVal
+= 'targetNS = None; '
324 strVal
+= 'targetNS = "%s"; ' % (self
.targetNS
)
325 if self
.defaultNS
== None:
326 strVal
+= 'defaultNS = None; '
328 strVal
+= 'defaultNS = "%s"; ' % (self
.defaultNS
)
329 if len(self
.nsMap
) == 0:
330 strVal
+= 'nsMap = None; '
332 strVal
+= 'nsMap = {'
333 for ik
, iv
in self
.nsMap
.iteritems():
334 strVal
+= '%s=%s; ' % (ik
,iv
)
338 def setKnownTypes(self
, masterKnownTypes
, masterKnownNamespaces
, parentNSE
):
339 # if we're a nested element, extend our parent element's mapping
340 if parentNSE
!= None:
341 self
.knownTypes
= parentNSE
.knownTypes
.copy()
342 # but if we have a different default namespace, replace the parent's default mappings
343 if (self
.defaultNS
!= None) and (parentNSE
.defaultNS
!= self
.defaultNS
):
344 newKT
= self
.knownTypes
.copy()
346 if tag
.find(':') < 0:
347 del self
.knownTypes
[tag
]
348 newMap
= parentNSE
.nsMap
.copy()
350 for k
, v
in self
.nsMap
.iteritems():
356 # TODO: instead of starting with the knownNamespaces, start with the "xmlms" mappings
357 # for this element. Then we'd only process the namespaces and tags we need to.
358 # But for now, this works.
359 for long, short
in masterKnownNamespaces
.iteritems():
360 reversedKNS
[short
] = long
361 mapLongs
= self
.nsMap
.values()
362 for tag
, mapClass
in masterKnownTypes
.iteritems():
364 if i
>= 0: # e.g. "wsdl:description"
365 knownTagShort
= tag
[:i
] # "wsdl"
366 knownTagName
= tag
[i
+1:] # "description"
367 knownTagLong
= reversedKNS
[knownTagShort
] # e.g. "http://schemas.xmlsoap.org/wsdl"
368 if (knownTagLong
in mapLongs
):
369 for mShort
, mLong
in self
.nsMap
.iteritems():
370 if mLong
== knownTagLong
:
371 actualShort
= mShort
# e.g. "ws"
372 actualTag
= '%s:%s' % (actualShort
, knownTagName
)
373 self
.knownTypes
[actualTag
] = mapClass
375 if self
.defaultNS
== knownTagLong
:
376 self
.knownTypes
[knownTagName
] = mapClass
377 else: # e.g. "ItemSearchRequest"
378 self
.knownTypes
[tag
] = mapClass
380 def expandQName(self
, eName
, attrName
, attrValue
):
382 i
= attrValue
.rfind(':')
384 if self
.defaultNS
!= None:
385 bigValue
= '%s:%s' % (self
.defaultNS
, attrValue
)
387 attrNS
= attrValue
[:i
]
388 attrNCName
= attrValue
[i
+1:]
389 for shortNs
, longNs
in self
.nsMap
.iteritems():
390 if shortNs
== attrNS
:
391 bigValue
= '%s:%s' % (longNs
, attrNCName
)
395 class XMLObjectFactory(xml
.sax
.ContentHandler
):
396 def __init__(self
, knownTypes
=None, knownNamespaces
=None, xmlSource
=None, createGenerics
=False):
397 self
.rootelement
= None
398 if xmlSource
== None:
399 self
.xmlSource
= "unknown"
401 self
.xmlSource
= xmlSource
402 self
.createGenerics
= createGenerics
404 self
.elementstack
= []
406 self
.collectContent
= None
407 if (knownNamespaces
== None):
408 self
.knownNamespaces
= {}
410 self
.knownNamespaces
= knownNamespaces
411 self
.reversedNamespaces
= {}
412 for longns
, shortns
in self
.knownNamespaces
.iteritems():
413 self
.reversedNamespaces
[shortns
] = longns
415 if (knownTypes
!= None):
416 for tag
, cls
in knownTypes
.iteritems():
421 if not self
.reversedNamespaces
.has_key(shortns
):
422 errorString
= 'Error unmarshalling XML document from source "%s": knownTypes specifies an unmapped short namespace "%s" for element "%s"' % (self
.xmlSource
, shortns
, tag
)
423 raise UnmarshallerException(errorString
)
424 longns
= self
.reversedNamespaces
[shortns
]
425 tag
= '%s:%s' % (longns
, tag
)
426 self
.knownTypes
[tag
] = cls
427 #printKnownTypes(self.knownTypes, 'Unmarshaller.XMLObjectFactory.__init__')
428 xml
.sax
.handler
.ContentHandler
.__init
__(self
)
430 def appendElementStack(self
, newElement
, newNS
):
431 self
.elementstack
.append(newElement
)
432 if (len(self
.nsstack
) > 0):
433 oldNS
= self
.nsstack
[-1]
434 if newNS
.defaultNS
== None:
435 newNS
.defaultNS
= oldNS
.defaultNS
436 if newNS
.targetNS
== None:
437 newNS
.targetNS
= oldNS
.targetNS
438 if len(newNS
.nsMap
) == 0:
439 newNS
.nsMap
= oldNS
.nsMap
440 elif len(oldNS
.nsMap
) > 0:
441 map = oldNS
.nsMap
.copy()
442 map.update(newNS
.nsMap
)
444 self
.nsstack
.append(newNS
)
447 def popElementStack(self
):
448 element
= self
.elementstack
.pop()
449 nse
= self
.nsstack
.pop()
452 ## ContentHandler methods
453 def startElement(self
, name
, attrs
):
454 ## print '[startElement] <%s>' % (name)
455 if name
== 'xs:annotation' or name
== 'xsd:annotation': # should use namespace mapping here
457 self
.appendElementStack(Element(name
, attrs
.copy()), NsElement())
460 if self
.collectContent
!= None:
461 strVal
= '<%s' % (name
)
462 for aKey
, aVal
in attrs
.items():
463 strVal
+= (' %s="%s"' % (aKey
, aVal
))
465 self
.collectContent
.content
+= strVal
473 element
= Element(name
, attrs
.copy(), xsname
=xsname
)
474 # if the element has namespace attributes, process them and add them to our stack
477 for k
in attrs
.getNames():
478 if k
.startswith('xmlns'):
480 eLongNs
= longNs
+ '/'
481 if str(eLongNs
) in asDict(self
.knownNamespaces
):
484 nse
.defaultNS
= longNs
487 nse
.nsMap
[shortNs
] = longNs
488 elif k
== 'targetNamespace':
489 nse
.targetNS
= attrs
.getValue(k
)
491 objtype
= attrs
.getValue(k
)
492 nse
= self
.appendElementStack(element
, nse
)
494 if nse
.nsMap
.has_key(nsname
):
495 longname
= '%s:%s' % (nse
.nsMap
[nsname
], name
)
496 ## elif objtype == None:
497 ## errorString = 'Error unmarshalling XML document from source "%s": tag "%s" at line "%d", column "%d" has an undefined namespace' % (self.xmlSource, xsname, self._locator.getLineNumber(), self._locator.getColumnNumber())
498 ## raise UnmarshallerException(errorString)
499 elif self
.reversedNamespaces
.has_key(nsname
):
500 longname
= '%s:%s' % (self
.reversedNamespaces
[nsname
], name
)
503 elif nse
.defaultNS
!= None:
504 longname
= '%s:%s' % (nse
.defaultNS
, name
)
507 element
.objtype
= objtype
508 element
.objclass
= self
.knownTypes
.get(longname
)
509 if element
.objclass
== None and len(self
.knownNamespaces
) == 0:
510 # handles common case where tags are unqualified and knownTypes are too, but there's a defaultNS
511 element
.objclass
= self
.knownTypes
.get(name
)
512 if (hasattr(element
.objclass
, "__xmlcontent__")):
513 self
.collectContent
= element
515 def characters(self
, content
):
516 ## print '[characters] "%s" (%s)' % (content, type(content))
517 if (content
!= None):
518 if self
.collectContent
!= None:
519 self
.collectContent
.content
+= content
521 self
.elementstack
[-1].content
+= content
523 def endElement(self
, name
):
524 ## print "[endElement] </%s>" % name
527 if i
>= 0: # Strip namespace prefixes for now until actually looking them up in xsd
530 if xsname
== "xs:annotation" or xsname
== "xsd:annotation": # here too
532 self
.popElementStack()
534 if self
.collectContent
!= None:
535 if xsname
!= self
.collectContent
.xsname
:
536 self
.collectContent
.content
+= ('</%s>' % (xsname
))
537 self
.popElementStack()
540 self
.collectContent
= None
541 oldChildren
= self
.elementstack
[-1].children
542 element
, nse
= self
.popElementStack()
543 if ((len(self
.elementstack
) > 1) and (self
.elementstack
[-1].getobjtype() == "None")):
544 parentElement
= self
.elementstack
[-2]
545 elif (len(self
.elementstack
) > 0):
546 parentElement
= self
.elementstack
[-1]
547 objtype
= element
.getobjtype()
548 if (objtype
== "None"):
550 constructorarglist
= []
551 if (len(element
.content
) > 0):
552 strippedElementContent
= element
.content
.strip()
553 if (len(strippedElementContent
) > 0):
554 constructorarglist
.append(element
.content
)
555 # If the element requires an object, but none is known, use the GenericXMLObject class
556 if ((element
.objclass
== None) and (element
.attrs
.get("objtype") == None) and ((len(element
.attrs
) > 0) or (len(element
.children
) > 0))):
557 if self
.createGenerics
:
558 element
.objclass
= GenericXMLObject
559 obj
= _objectfactory(objtype
, constructorarglist
, element
.objclass
)
560 if element
.objclass
== GenericXMLObject
:
561 obj
.setXMLAttributes(str(xsname
), element
.attrs
, element
.children
, nse
.nsMap
, nse
.defaultNS
)
562 complexType
= getComplexType(obj
)
564 if (hasattr(obj
, "__xmlname__") and getattr(obj
, "__xmlname__") == "sequence"):
565 self
.elementstack
[-1].children
= oldChildren
567 if (len(element
.attrs
) > 0) and not isinstance(obj
, list):
568 for attrname
, attr
in element
.attrs
.items():
569 if attrname
== XMLNS
or attrname
.startswith(XMLNS_PREFIX
):
570 if attrname
.startswith(XMLNS_PREFIX
):
571 ns
= attrname
[XMLNS_PREFIX_LENGTH
:]
574 if complexType
!= None or element
.objclass
== GenericXMLObject
:
575 if not hasattr(obj
, "__xmlnamespaces__"):
576 obj
.__xmlnamespaces
__ = {ns:attr}
577 elif ns
not in obj
.__xmlnamespaces
__:
578 if (hasattr(obj
.__class
__, "__xmlnamespaces__")
579 and (obj
.__xmlnamespaces
__ is obj
.__class
__.__xmlnamespaces
__)):
580 obj
.__xmlnamespaces
__ = dict(obj
.__xmlnamespaces
__)
581 obj
.__xmlnamespaces
__[ns
] = attr
582 elif not attrname
== "objtype":
583 if attrname
.find(":") > -1: # Strip namespace prefixes for now until actually looking them up in xsd
584 attrname
= attrname
[attrname
.find(":") + 1:]
585 if (complexType
!= None):
586 xsdElement
= complexType
.findElement(attrname
)
587 if (xsdElement
!= None):
588 type = xsdElement
.type
590 if (type == TYPE_QNAME
):
591 attr
= nse
.expandQName(name
, attrname
, attr
)
592 type = xsdToLangType(type)
593 ### ToDO remove maxOccurs hack after bug 177 is fixed
594 if attrname
== "maxOccurs" and attr
== "unbounded":
597 attr
= _objectfactory(type, attr
)
598 except Exception, exceptData
:
599 errorString
= 'Error unmarshalling attribute "%s" at line %d, column %d in XML document from source "%s": %s' % (attrname
, self
._locator
.getLineNumber(), self
._locator
.getColumnNumber(), self
.xmlSource
, str(exceptData
))
600 raise UnmarshallerException(errorString
)
602 setattrignorecase(obj
, _toAttrName(obj
, attrname
), attr
)
603 except AttributeError:
604 errorString
= 'Error setting value of attribute "%s" at line %d, column %d in XML document from source "%s": object type of XML element "%s" is not specified or known' % (attrname
, self
._locator
.getLineNumber(), self
._locator
.getColumnNumber(), self
.xmlSource
, name
)
605 raise UnmarshallerException(errorString
)
606 ## obj.__dict__[_toAttrName(obj, attrname)] = attr
607 # stuff any child attributes meant to be in a sequence via the __xmlflattensequence__
609 if hasattr(obj
, "__xmlflattensequence__"):
610 flatten
= obj
.__xmlflattensequence
__
611 if (isinstance(flatten
, dict)):
612 for sequencename
, xmlnametuple
in flatten
.items():
613 if (xmlnametuple
== None):
614 flattenDict
[sequencename
] = sequencename
615 elif (not isinstance(xmlnametuple
, (tuple, list))):
616 flattenDict
[str(xmlnametuple
)] = sequencename
618 for xmlname
in xmlnametuple
:
619 flattenDict
[xmlname
] = sequencename
621 raise Exception("Invalid type for __xmlflattensequence___ : it must be a dict")
623 # reattach an object"s attributes to it
624 for childname
, child
in element
.children
:
625 if (childname
in flattenDict
):
626 sequencename
= _toAttrName(obj
, flattenDict
[childname
])
627 if (not hasattr(obj
, sequencename
)):
628 obj
.__dict
__[sequencename
] = []
629 sequencevalue
= getattr(obj
, sequencename
)
630 if (sequencevalue
== None):
631 obj
.__dict
__[sequencename
] = []
632 sequencevalue
= getattr(obj
, sequencename
)
633 sequencevalue
.append(child
)
634 elif (objtype
== "list"):
636 elif isinstance(obj
, dict):
637 if (childname
== DICT_ITEM_NAME
):
638 obj
[child
[DICT_ITEM_KEY_NAME
]] = child
[DICT_ITEM_VALUE_NAME
]
640 obj
[childname
] = child
642 # don't replace a good attribute value with a bad one
643 childAttrName
= _toAttrName(obj
, childname
)
644 if (not hasattr(obj
, childAttrName
)) or (getattr(obj
, childAttrName
) == None) or (getattr(obj
, childAttrName
) == []) or (not isinstance(child
, GenericXMLObject
)):
646 setattrignorecase(obj
, childAttrName
, child
)
647 except AttributeError:
648 raise MarshallerException("Error unmarshalling child element \"%s\" of XML element \"%s\": object type not specified or known" % (childname
, name
))
650 if (complexType
!= None):
651 for element
in complexType
.elements
:
653 elementName
= _toAttrName(obj
, element
.name
)
654 if ((elementName
not in obj
.__dict
__) or (obj
.__dict
__[elementName
] == None)):
655 langType
= xsdToLangType(element
.type)
656 defaultValue
= _objectfactory(langType
, element
.default
)
657 obj
.__dict
__[elementName
] = defaultValue
660 if (isinstance(obj
, list)):
661 if ((element
.attrs
.has_key("mutable")) and (element
.attrs
.getValue("mutable") == "false")):
665 if (len(self
.elementstack
) > 0):
666 ## print "[endElement] appending child with name: ", name, "; objtype: ", objtype
667 parentElement
.children
.append((name
, obj
))
669 self
.rootelement
= obj
671 def getRootObject(self
):
672 return self
.rootelement
674 def _toAttrName(obj
, name
):
675 if (hasattr(obj
, "__xmlrename__")):
676 for key
, val
in obj
.__xmlrename
__.iteritems():
680 ## if (name.startswith("__") and not name.endswith("__")):
681 ## name = "_%s%s" % (obj.__class__.__name__, name)
684 def printKnownTypes(kt
, where
):
685 print 'KnownTypes from %s' % (where
)
686 for tag
, cls
in kt
.iteritems():
687 print '%s => %s' % (tag
, str(cls
))
689 __typeMappingXsdToLang
= {
693 "date": "str", # ToDO Need to work out how to create lang date types
695 "decimal": "float", # ToDO Does python have a better fixed point type?
702 "unicode": "unicode",
704 "duration": "str", # see above (date)
705 "datetime": "str", # see above (date)
706 "time": "str", # see above (date)
709 "blob" : "str", # ag:blob
710 "currency" : "str", # ag:currency
713 def xsdToLangType(xsdType
):
714 if xsdType
.startswith(XMLSCHEMA_XSD_URL
):
715 xsdType
= xsdType
[len(XMLSCHEMA_XSD_URL
)+1:]
716 elif xsdType
.startswith(AG_URL
):
717 xsdType
= xsdType
[len(AG_URL
)+1:]
718 langType
= __typeMappingXsdToLang
.get(xsdType
)
719 if (langType
== None):
720 raise Exception("Unknown xsd type %s" % xsdType
)
723 def langToXsdType(langType
):
724 if langType
in asDict(__typeMappingXsdToLang
):
725 return '%s:%s' % (XMLSCHEMA_XSD_URL
, langType
)
728 def _getXmlValue(langValue
):
729 if (isinstance(langValue
, bool)):
730 return str(langValue
).lower()
731 elif (isinstance(langValue
, unicode)):
732 return langValue
.encode()
734 return str(langValue
)
736 def unmarshal(xmlstr
, knownTypes
=None, knownNamespaces
=None, xmlSource
=None, createGenerics
=False):
737 objectfactory
= XMLObjectFactory(knownTypes
, knownNamespaces
, xmlSource
, createGenerics
)
738 # on Linux, pyXML's sax.parseString fails when passed unicode
739 if (not sysutils
.isWindows()):
742 xml
.sax
.parseString(xmlstr
, objectfactory
)
743 except xml
.sax
.SAXParseException
, errorData
:
744 if xmlSource
== None:
745 xmlSource
= 'unknown'
746 errorString
= 'SAXParseException ("%s") detected at line %d, column %d in XML document from source "%s" ' % (errorData
.getMessage(), errorData
.getLineNumber(), errorData
.getColumnNumber(), xmlSource
)
747 raise UnmarshallerException(errorString
)
748 return objectfactory
.getRootObject()
750 def marshal(obj
, elementName
=None, prettyPrint
=False, marshalType
=True, indent
=0, knownTypes
=None, knownNamespaces
=None, encoding
=-1):
751 worker
= XMLMarshalWorker(prettyPrint
=prettyPrint
, marshalType
=marshalType
, knownTypes
=knownTypes
, knownNamespaces
=knownNamespaces
)
752 if obj
!= None and hasattr(obj
, '__xmldeepexclude__'):
753 worker
.xmldeepexclude
= obj
.__xmldeepexclude
__
754 xmlstr
= "".join(worker
._marshal
(obj
, elementName
, indent
=indent
))
755 aglogging
.info(xmlMarshallerLogger
, "marshal produced string of type %s", type(xmlstr
))
756 if (encoding
== None):
758 if (not isinstance(encoding
, basestring
)):
759 encoding
= sys
.getdefaultencoding()
760 if (not isinstance(xmlstr
, unicode)):
761 xmlstr
= xmlstr
.decode()
762 xmlstr
= u
'<?xml version="1.0" encoding="%s"?>\n%s' % (encoding
, xmlstr
)
763 return xmlstr
.encode(encoding
)
765 class XMLMarshalWorker(object):
766 def __init__(self
, marshalType
=True, prettyPrint
=False, knownTypes
=None, knownNamespaces
=None):
767 if knownTypes
== None:
770 self
.knownTypes
= knownTypes
771 if knownNamespaces
== None:
772 self
.knownNamespaces
= {}
774 self
.knownNamespaces
= knownNamespaces
775 self
.prettyPrint
= prettyPrint
776 self
.marshalType
= marshalType
777 self
.xmldeepexclude
= []
780 def getNSPrefix(self
):
781 if len(self
.nsstack
) > 0:
782 return self
.nsstack
[-1].prefix
785 def isKnownType(self
, elementName
):
787 nse
= self
.nsstack
[-1]
788 i
= elementName
.rfind(':')
790 prefix
= elementName
[:i
]
791 name
= elementName
[i
+1:]
793 prefix
= DEFAULT_NAMESPACE_KEY
795 for shortNs
, longNs
in nse
.nameSpaces
.iteritems():
796 if shortNs
== prefix
:
799 if tagLongNs
== None:
800 knownTagName
= elementName
802 knownShortNs
= self
.knownNamespaces
[tagLongNs
]
803 knownTagName
= knownShortNs
+ ':' + name
804 if (knownTagName
in asDict(self
.knownTypes
)):
805 knownClass
= self
.knownTypes
[knownTagName
]
809 def popNSStack(self
):
812 def appendNSStack(self
, obj
):
815 for nse
in self
.nsstack
:
816 for k
, v
in nse
.nsMap
.iteritems():
818 if k
== DEFAULT_NAMESPACE_KEY
:
822 if hasattr(obj
, "__xmlnamespaces__"):
823 ns
= getattr(obj
, "__xmlnamespaces__")
826 for nameSpaceKey
in keys
:
827 nameSpaceUrl
= ns
[nameSpaceKey
]
828 if nameSpaceUrl
in nameSpaces
.values():
829 for k
, v
in nameSpaces
.iteritems():
830 if v
== nameSpaceUrl
:
834 if nameSpaceKey
== "":
835 defaultLongNS
= nameSpaceUrl
836 nameSpaces
[DEFAULT_NAMESPACE_KEY
] = nameSpaceUrl
837 newNS
.nsMap
[DEFAULT_NAMESPACE_KEY
] = nameSpaceUrl
838 nameSpaceAttrs
+= ' xmlns="%s" ' % (nameSpaceUrl
)
840 nameSpaces
[nameSpaceKey
] = nameSpaceUrl
841 newNS
.nsMap
[nameSpaceKey
] = nameSpaceUrl
842 nameSpaceAttrs
+= ' xmlns:%s="%s" ' % (nameSpaceKey
, nameSpaceUrl
)
843 nameSpaceAttrs
= nameSpaceAttrs
.rstrip()
844 if len(self
.nsstack
) > 0:
845 newNS
.prefix
= self
.nsstack
[-1].prefix
848 if obj
!= None and hasattr(obj
, "__xmldefaultnamespace__"):
849 longPrefixNS
= getattr(obj
, "__xmldefaultnamespace__")
850 if longPrefixNS
== defaultLongNS
:
854 for k
, v
in nameSpaces
.iteritems():
855 if v
== longPrefixNS
:
856 newNS
.prefix
= k
+ ':'
859 if (longPrefixNS
in asDict(self
.knownNamespaces
)):
860 newNS
.prefix
= self
.knownNamespaces
[longPrefixNS
] + ':'
862 raise MarshallerException('Error marshalling __xmldefaultnamespace__ ("%s") not defined in namespace stack' % (longPrefixNS
))
863 if obj
!= None and hasattr(obj
, "targetNamespace"):
864 newNS
.targetNS
= obj
.targetNamespace
865 elif len(self
.nsstack
) > 0:
866 newNS
.targetNS
= self
.nsstack
[-1].targetNS
867 newNS
.nameSpaces
= nameSpaces
868 self
.nsstack
.append(newNS
)
869 return nameSpaceAttrs
871 def contractQName(self
, value
, obj
, attr
):
872 value
= langToXsdType(value
)
877 # the value doesn't have a namespace and we couldn't map it to an XSD type...what to do?
878 # (a) just write it, as is, and hope it's in the default namespace (for now)
879 # (b) throw an exception so we can track down the bad code (later)
881 if (longNS
in self
.nsstack
[-1].nameSpaces
.values()):
882 for kShort
, vLong
in self
.nsstack
[-1].nameSpaces
.iteritems():
887 shortNS
= longNS
# if we can't find the long->short mappping, just use longNS
888 if shortNS
== DEFAULT_NAMESPACE_KEY
:
891 value
= shortNS
+ ':' + value
[i
+1:]
894 def _genObjTypeStr(self
, typeString
):
896 return ' objtype="%s"' % typeString
899 def _marshal(self
, obj
, elementName
=None, nameSpacePrefix
="", indent
=0):
901 aglogging
.debug(xmlMarshallerLogger
, "--> _marshal: elementName=%s%s, type=%s, obj=%s, indent=%d", nameSpacePrefix
, elementName
, type(obj
), str(obj
), indent
)
903 aglogging
.debug(xmlMarshallerLogger
, "--> _marshal: elementName=%s%s, obj is None, indent=%d", nameSpacePrefix
, elementName
, indent
)
904 if ((obj
!= None) and (hasattr(obj
, 'preMarshal'))):
907 excludeAttrs
.extend(self
.xmldeepexclude
)
908 if hasattr(obj
, "__xmlexclude__"):
909 excludeAttrs
.extend(obj
.__xmlexclude
__)
910 prettyPrint
= self
.prettyPrint
911 knownTypes
= self
.knownTypes
913 if self
.prettyPrint
or indent
:
921 ## Determine the XML element name. If it isn"t specified in the
922 ## parameter list, look for it in the __xmlname__ attribute,
923 ## else use the default generic BASETYPE_ELEMENT_NAME.
924 nameSpaceAttrs
= self
.appendNSStack(obj
)
925 nameSpacePrefix
= self
.getNSPrefix()
927 if hasattr(obj
, "__xmlname__"):
928 elementName
= nameSpacePrefix
+ obj
.__xmlname
__
930 elementName
= nameSpacePrefix
+ BASETYPE_ELEMENT_NAME
932 elementName
= nameSpacePrefix
+ elementName
934 if (hasattr(obj
, "__xmlsequencer__")) and (obj
.__xmlsequencer
__ != None):
935 if (XMLSCHEMA_XSD_URL
in self
.nsstack
[-1].nameSpaces
.values()):
936 for kShort
, vLong
in self
.nsstack
[-1].nameSpaces
.iteritems():
937 if vLong
== XMLSCHEMA_XSD_URL
:
938 if kShort
!= DEFAULT_NAMESPACE_KEY
:
939 xsdPrefix
= kShort
+ ':'
945 elementAdd
= xsdPrefix
+ obj
.__xmlsequencer
__
950 ## Add more members_to_skip based on ones the user has selected
951 ## via the __xmlexclude__ and __xmldeepexclude__ attributes.
952 members_to_skip
.extend(excludeAttrs
)
953 # Marshal the attributes that are selected to be XML attributes.
955 className
= ag_className(obj
)
956 classNamePrefix
= "_" + className
957 if hasattr(obj
, "__xmlattributes__"):
958 xmlattributes
= obj
.__xmlattributes
__
959 members_to_skip
.extend(xmlattributes
)
960 for attr
in xmlattributes
:
961 internalAttrName
= attr
963 if (attr
.startswith("__") and not attr
.endswith("__")):
964 internalAttrName
= classNamePrefix
+ attr
966 # Fail silently if a python attribute is specified to be
967 # an XML attribute but is missing.
968 attrNameSpacePrefix
= ""
969 if hasattr(obj
, "__xmlattrnamespaces__"):
970 for nameSpaceKey
, nameSpaceAttributes
in getattr(obj
, "__xmlattrnamespaces__").iteritems():
971 if nameSpaceKey
== nameSpacePrefix
[:-1]: # Don't need to specify attribute namespace if it is the same as its element
973 if attr
in nameSpaceAttributes
:
974 attrNameSpacePrefix
= nameSpaceKey
+ ":"
977 value
= attrs
.get(internalAttrName
)
978 if (hasattr(obj
, "__xmlrename__") and attr
in asDict(obj
.__xmlrename
__)):
979 attr
= obj
.__xmlrename
__[attr
]
981 complexType
= getComplexType(obj
)
982 if (complexType
!= None):
983 xsdElement
= complexType
.findElement(attr
)
984 if (xsdElement
!= None):
985 default
= xsdElement
.default
986 if (default
!= None):
987 if ((default
== value
) or (default
== _getXmlValue(value
))):
992 elif xsdElement
.type == TYPE_QNAME
:
993 value
= self
.contractQName(value
, obj
, attr
)
997 # ToDO remove maxOccurs hack after bug 177 is fixed
998 if attr
== "maxOccurs" and value
== -1:
1001 if isinstance(value
, bool):
1007 value
= objutils
.toDiffableRepr(value
)
1009 objattrs
+= ' %s%s="%s"' % (attrNameSpacePrefix
, attr
, utillang
.escape(value
))
1012 elif isinstance(obj
, bool):
1013 objTypeStr
= self
._genObjTypeStr
("bool")
1014 xmlString
= ['%s<%s%s>%s</%s>%s' % (prefix
, elementName
, objTypeStr
, obj
, elementName
, newline
)]
1015 elif isinstance(obj
, int):
1016 objTypeStr
= self
._genObjTypeStr
("int")
1017 xmlString
= ['%s<%s%s>%s</%s>%s' % (prefix
, elementName
, objTypeStr
, str(obj
), elementName
, newline
)]
1018 elif isinstance(obj
, long):
1019 objTypeStr
= self
._genObjTypeStr
("long")
1020 xmlString
= ['%s<%s%s>%s</%s>%s' % (prefix
, elementName
, objTypeStr
, str(obj
), elementName
, newline
)]
1021 elif isinstance(obj
, float):
1022 objTypeStr
= self
._genObjTypeStr
("float")
1023 xmlString
= ['%s<%s%s>%s</%s>%s' % (prefix
, elementName
, objTypeStr
, str(obj
), elementName
, newline
)]
1024 elif isinstance(obj
, unicode): # have to check before basestring - unicode is instance of base string
1025 xmlString
= ['%s<%s>%s</%s>%s' % (prefix
, elementName
, utillang
.escape(obj
.encode()), elementName
, newline
)]
1026 elif isinstance(obj
, basestring
):
1027 xmlString
= ['%s<%s>%s</%s>%s' % (prefix
, elementName
, utillang
.escape(obj
), elementName
, newline
)]
1028 elif isinstance(obj
, datetime
.datetime
):
1029 objTypeStr
= self
._genObjTypeStr
("datetime")
1030 xmlString
= ['%s<%s%s>%s</%s>%s' % (prefix
, elementName
, objTypeStr
, str(obj
), elementName
, newline
)]
1031 elif isinstance(obj
, datetime
.date
):
1032 objTypeStr
= self
._genObjTypeStr
("date")
1033 xmlString
= ['%s<%s%s>%s</%s>%s' % (prefix
, elementName
, objTypeStr
, str(obj
), elementName
, newline
)]
1034 elif isinstance(obj
, datetime
.time
):
1035 objTypeStr
= self
._genObjTypeStr
("time")
1036 xmlString
= ['%s<%s%s>%s</%s>%s' % (prefix
, elementName
, objTypeStr
, str(obj
), elementName
, newline
)]
1037 elif isinstance(obj
, list):
1041 objTypeStr
= self
._genObjTypeStr
("list")
1042 xmlString
= ['%s<%s%s>%s' % (prefix
, elementName
, objTypeStr
, newline
)]
1044 xmlString
.extend(self
._marshal
(item
, indent
=indent
+increment
))
1045 xmlString
.append("%s</%s>%s" % (prefix
, elementName
, newline
))
1046 elif isinstance(obj
, tuple):
1050 objTypeStr
= self
._genObjTypeStr
("list")
1051 xmlString
= ['%s<%s%s mutable="false">%s' % (prefix
, elementName
, objTypeStr
, newline
)]
1053 xmlString
.extend(self
._marshal
(item
, indent
=indent
+increment
))
1054 xmlString
.append("%s</%s>%s" % (prefix
, elementName
, newline
))
1055 elif isinstance(obj
, dict):
1056 objTypeStr
= self
._genObjTypeStr
("dict")
1057 xmlString
= ['%s<%s%s>%s' % (prefix
, elementName
, objTypeStr
, newline
)]
1058 subprefix
= prefix
+ " "*increment
1059 subindent
= indent
+ 2*increment
1063 xmlString
.append("%s<%s>%s" % (subprefix
, DICT_ITEM_NAME
, newline
))
1064 xmlString
.extend(self
._marshal
(key
, elementName
=DICT_ITEM_KEY_NAME
, indent
=subindent
))
1065 xmlString
.extend(self
._marshal
(obj
[key
], elementName
=DICT_ITEM_VALUE_NAME
, indent
=subindent
))
1066 xmlString
.append("%s</%s>%s" % (subprefix
, DICT_ITEM_NAME
, newline
))
1067 xmlString
.append("%s</%s>%s" % (prefix
, elementName
, newline
))
1068 elif hasattr(obj
, "__xmlcontent__"):
1069 contentValue
= getattr(obj
, obj
.__xmlcontent
__)
1070 if contentValue
== None:
1071 xmlString
= ["%s<%s%s%s/>%s" % (prefix
, elementName
, nameSpaceAttrs
, objattrs
, newline
)]
1073 contentValue
= utillang
.escape(contentValue
)
1074 xmlString
= ["%s<%s%s%s>%s</%s>%s" % (prefix
, elementName
, nameSpaceAttrs
, objattrs
, contentValue
, elementName
, newline
)]
1076 # Only add the objtype if the element tag is unknown to us.
1077 if (isinstance(obj
, GenericXMLObject
)):
1079 elif (self
.isKnownType(elementName
) == True):
1082 objTypeStr
= self
._genObjTypeStr
("%s.%s" % (obj
.__class
__.__module
__, className
))
1083 xmlString
= ['%s<%s%s%s%s' % (prefix
, elementName
, nameSpaceAttrs
, objattrs
, objTypeStr
)]
1084 # get the member, value pairs for the object, filtering out the types we don"t support
1085 if (elementAdd
!= None):
1086 prefix
+= increment
*" "
1088 xmlMemberString
= []
1089 if hasattr(obj
, "__xmlbody__"):
1090 xmlbody
= getattr(obj
, obj
.__xmlbody
__)
1092 xmlMemberString
.append(utillang
.escape(xmlbody
))
1094 if hasattr(obj
, "__xmlattrgroups__"):
1095 attrGroups
= obj
.__xmlattrgroups
__.copy()
1096 if (not isinstance(attrGroups
, dict)):
1097 raise "__xmlattrgroups__ is not a dict, but must be"
1098 for n
in attrGroups
.iterkeys():
1099 members_to_skip
.extend(attrGroups
[n
])
1102 # add the list of all attributes to attrGroups
1103 eList
= obj
.__dict
__.keys()
1105 attrGroups
["__nogroup__"] = eList
1107 for eName
, eList
in attrGroups
.iteritems():
1108 if (eName
!= "__nogroup__"):
1109 prefix
+= increment
*" "
1111 objTypeStr
= self
._genObjTypeStr
("None")
1112 xmlMemberString
.append('%s<%s%s>%s' % (prefix
, eName
, objTypeStr
, newline
))
1114 value
= obj
.__dict
__[name
]
1115 if eName
== "__nogroup__" and name
in members_to_skip
: continue
1116 if name
.startswith("__") and name
.endswith("__"): continue
1117 if (hasattr(obj
, "__xmlcdatacontent__") and (obj
.__xmlcdatacontent
__ == name
)):
1119 subElementNameSpacePrefix
= nameSpacePrefix
1120 if hasattr(obj
, "__xmlattrnamespaces__"):
1121 for nameSpaceKey
, nameSpaceValues
in getattr(obj
, "__xmlattrnamespaces__").iteritems():
1122 if name
in nameSpaceValues
:
1123 subElementNameSpacePrefix
= nameSpaceKey
+ ":"
1125 # handle sequences listed in __xmlflattensequence__
1126 # specially: instead of listing the contained items inside
1127 # of a separate list, as God intended, list them inside
1128 # the object containing the sequence.
1129 if (hasattr(obj
, "__xmlflattensequence__") and (value
!= None) and (name
in asDict(obj
.__xmlflattensequence
__))):
1130 xmlnametuple
= obj
.__xmlflattensequence
__[name
]
1131 if (xmlnametuple
== None):
1132 xmlnametuple
= [name
]
1133 elif (not isinstance(xmlnametuple
, (tuple,list))):
1134 xmlnametuple
= [str(xmlnametuple
)]
1136 if (len(xmlnametuple
) == 1):
1137 xmlname
= xmlnametuple
[0]
1138 if not isinstance(value
, (list, tuple)):
1140 for seqitem
in value
:
1141 xmlMemberString
.extend(self
._marshal
(seqitem
, xmlname
, subElementNameSpacePrefix
, indent
=indent
+increment
))
1143 if (hasattr(obj
, "__xmlrename__") and name
in asDict(obj
.__xmlrename
__)):
1144 xmlname
= obj
.__xmlrename
__[name
]
1148 xmlMemberString
.extend(self
._marshal
(value
, xmlname
, subElementNameSpacePrefix
, indent
=indent
+increment
))
1149 if (eName
!= "__nogroup__"):
1150 xmlMemberString
.append("%s</%s>%s" % (prefix
, eName
, newline
))
1151 prefix
= prefix
[:-increment
]
1154 # if we have nested elements, add them here, otherwise close the element tag immediately.
1156 for s
in xmlMemberString
:
1157 if (len(s
) > 0): newList
.append(s
)
1158 xmlMemberString
= newList
1159 if len(xmlMemberString
) > 0:
1160 xmlString
.append(">")
1161 if hasattr(obj
, "__xmlbody__"):
1162 xmlString
.extend(xmlMemberString
)
1163 xmlString
.append("</%s>%s" % (elementName
, newline
))
1165 xmlString
.append(newline
)
1166 if (elementAdd
!= None):
1167 xmlString
.append("%s<%s>%s" % (prefix
, elementAdd
, newline
))
1168 xmlString
.extend(xmlMemberString
)
1169 if (elementAdd
!= None):
1170 xmlString
.append("%s</%s>%s" % (prefix
, elementAdd
, newline
))
1171 prefix
= prefix
[:-increment
]
1173 xmlString
.append("%s</%s>%s" % (prefix
, elementName
, newline
))
1175 if hasattr(obj
, "__xmlcdatacontent__"):
1176 cdataAttr
= obj
.__xmlcdatacontent
__
1177 cdataContent
= obj
.__dict
__[cdataAttr
]
1178 xmlString
.append("><![CDATA[%s]]></%s>%s" % (cdataContent
, elementName
, newline
))
1180 xmlString
.append("/>%s" % newline
)
1181 if aglogging
.isEnabledForDebug(xmlMarshallerLogger
):
1182 aglogging
.debug(xmlMarshallerLogger
, "<-- _marshal: %s", objutils
.toDiffableString(xmlString
))
1183 #print "<-- _marshal: %s" % str(xmlString)
1187 # A simple test, to be executed when the xmlmarshaller is run standalone
1188 class MarshallerPerson
:
1189 __xmlname__
= "person"
1190 __xmlexclude__
= ["fabulousness",]
1191 __xmlattributes__
= ("nonSmoker",)
1192 __xmlrename__
= {"_phoneNumber": "telephone"}
1193 __xmlflattensequence__
= {"favoriteWords": ("vocabulary",)}
1194 __xmlattrgroups__
= {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]}
1196 def setPerson(self
):
1197 self
.firstName
= "Albert"
1198 self
.lastName
= "Camus"
1199 self
.addressLine1
= "23 Absurd St."
1203 self
._phoneNumber
= "808-303-2323"
1204 self
.favoriteWords
= ["angst", "ennui", "existence"]
1205 self
.phobias
= ["war", "tuberculosis", "cars"]
1207 self
.fabulousness
= "tres tres"
1208 self
.nonSmoker
= False
1210 if isMain(__name__
):
1211 p1
= MarshallerPerson()
1213 xmlP1
= marshal(p1
, prettyPrint
=True, encoding
="utf-8")
1214 print "\n########################"
1215 print "# testPerson test case #"
1216 print "########################"
1218 p2
= unmarshal(xmlP1
)
1219 xmlP2
= marshal(p2
, prettyPrint
=True, encoding
="utf-8")
1221 print "Success: repeated marshalling yields identical results"
1223 print "Failure: repeated marshalling yields different results"