]>
Commit | Line | Data |
---|---|---|
1f780e48 RD |
1 | #---------------------------------------------------------------------------- |
2 | # Name: xmlmarshaller.py | |
3 | # Purpose: | |
4 | # | |
02b800ce | 5 | # Authors: John Spurling, Joel Hare, Alan Mullendore |
1f780e48 RD |
6 | # |
7 | # Created: 7/28/04 | |
8 | # CVS-ID: $Id$ | |
9 | # Copyright: (c) 2004-2005 ActiveGrid, Inc. | |
10 | # License: wxWindows License | |
11 | #---------------------------------------------------------------------------- | |
6f1a3f9c | 12 | import sys |
1f780e48 | 13 | from types import * |
02b800ce | 14 | from activegrid.util.lang import * |
2eeaec19 | 15 | import logging |
02b800ce | 16 | ifDefPy() |
1f780e48 RD |
17 | import xml.sax |
18 | import xml.sax.handler | |
02b800ce | 19 | endIfDef() |
2eeaec19 | 20 | import xml.sax.saxutils as saxutils |
02b800ce | 21 | import activegrid.util.objutils as objutils |
2eeaec19 | 22 | import activegrid.util.aglogging as aglogging |
6f1a3f9c RD |
23 | |
24 | MODULE_PATH = "__main__" | |
25 | ||
2eeaec19 | 26 | ## ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed |
1f780e48 RD |
27 | |
28 | """ | |
2eeaec19 | 29 | Special attributes that we recognize: |
1f780e48 RD |
30 | |
31 | name: __xmlname__ | |
32 | type: string | |
33 | description: the name of the xml element for the marshalled object | |
34 | ||
35 | name: __xmlattributes__ | |
36 | type: tuple or list | |
2eeaec19 | 37 | description: the name(s) of the Lang string attribute(s) to be |
1f780e48 | 38 | marshalled as xml attributes instead of nested xml elements. currently |
2eeaec19 | 39 | these can only be strings since there"s not a way to get the type |
1f780e48 RD |
40 | information back when unmarshalling. |
41 | ||
42 | name: __xmlexclude__ | |
43 | type: tuple or list | |
2eeaec19 | 44 | description: the name(s) of the lang attribute(s) to skip when |
1f780e48 RD |
45 | marshalling. |
46 | ||
47 | name: __xmlrename__ | |
48 | type: dict | |
2eeaec19 | 49 | description: describes an alternate Lang <-> XML name mapping. |
1f780e48 | 50 | Normally the name mapping is the identity function. __xmlrename__ |
2eeaec19 | 51 | overrides that. The keys are the Lang names, the values are their |
1f780e48 RD |
52 | associated XML names. |
53 | ||
54 | name: __xmlflattensequence__ | |
55 | type: dict, tuple, or list | |
2eeaec19 | 56 | description: the name(s) of the Lang sequence attribute(s) whose |
1f780e48 RD |
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.: | |
60 | ||
61 | myseq = (1, 2) | |
62 | <!-- normal way of marshalling --> | |
63 | <myseq> | |
2eeaec19 RD |
64 | <item objtype="int">1</item> |
65 | <item objtype="int">2</item> | |
1f780e48 | 66 | </myseq> |
2eeaec19 RD |
67 | <!-- with __xmlflattensequence__ set to {"myseq": "squish"} --> |
68 | <squish objtype="int">1</squish> | |
69 | <squish objtype="int">2</squish> | |
1f780e48 RD |
70 | |
71 | name: __xmlnamespaces__ | |
72 | type: dict | |
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.: | |
76 | ||
77 | __xmlnamespaces__ = { "xsd":"http://www.w3c.org/foo.xsd" } | |
78 | ||
79 | name: __xmldefaultnamespace__ | |
80 | type: String | |
81 | description: the prefix of a namespace defined in __xmlnamespaces__ that | |
82 | should be used as the default namespace for the object. | |
83 | ||
84 | name: __xmlattrnamespaces__ | |
85 | type: dict | |
2eeaec19 | 86 | description: a dict assigning the Lang object"s attributes to the namespaces |
1f780e48 RD |
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 | |
2eeaec19 | 89 | a list of the Lang attribute names. e.g.: |
1f780e48 RD |
90 | |
91 | __xmlattrnamespaces__ = { "ag":["firstName", "lastName", "addressLine1", "city"] } | |
92 | ||
bbf7159c RD |
93 | name: __xmlattrgroups__ |
94 | type: dict | |
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 | |
97 | within it. e.g. | |
98 | ||
99 | __xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]} | |
1f780e48 | 100 | |
02b800ce RD |
101 | name: __xmlcdatacontent__ |
102 | type: string | |
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. | |
105 | ||
106 | __xmlcdatacontent__ = "messyContent" | |
107 | ||
1f780e48 RD |
108 | """ |
109 | ||
2eeaec19 RD |
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 | |
115 | ||
1f780e48 RD |
116 | ################################################################################ |
117 | # | |
118 | # module exceptions | |
119 | # | |
120 | ################################################################################ | |
121 | ||
122 | class Error(Exception): | |
123 | """Base class for errors in this module.""" | |
124 | pass | |
125 | ||
126 | class UnhandledTypeException(Error): | |
127 | """Exception raised when attempting to marshal an unsupported | |
128 | type. | |
129 | """ | |
130 | def __init__(self, typename): | |
131 | self.typename = typename | |
132 | def __str__(self): | |
133 | return "%s is not supported for marshalling." % str(self.typename) | |
134 | ||
135 | class XMLAttributeIsNotStringType(Error): | |
2eeaec19 | 136 | """Exception raised when an object"s attribute is specified to be |
1f780e48 RD |
137 | marshalled as an XML attribute of the enclosing object instead of |
138 | a nested element. | |
139 | """ | |
140 | def __init__(self, attrname, typename): | |
141 | self.attrname = attrname | |
142 | self.typename = typename | |
143 | def __str__(self): | |
144 | return """%s was set to be marshalled as an XML attribute | |
2eeaec19 | 145 | instead of a nested element, but the object"s type is %s, not |
1f780e48 RD |
146 | string.""" % (self.attrname, self.typename) |
147 | ||
2eeaec19 | 148 | class MarshallerException(Exception): |
02b800ce RD |
149 | pass |
150 | ||
151 | class UnmarshallerException(Exception): | |
2eeaec19 RD |
152 | pass |
153 | ||
1f780e48 RD |
154 | ################################################################################ |
155 | # | |
156 | # constants and such | |
157 | # | |
158 | ################################################################################ | |
159 | ||
2eeaec19 RD |
160 | XMLNS = "xmlns" |
161 | XMLNS_PREFIX = XMLNS + ":" | |
1f780e48 | 162 | XMLNS_PREFIX_LENGTH = len(XMLNS_PREFIX) |
02b800ce RD |
163 | DEFAULT_NAMESPACE_KEY = "__DEFAULTNS__" |
164 | TYPE_QNAME = "QName" | |
165 | XMLSCHEMA_XSD_URL = "http://www.w3.org/2001/XMLSchema" | |
166 | AG_URL = "http://www.activegrid.com/ag.xsd" | |
1f780e48 | 167 | |
2eeaec19 RD |
168 | BASETYPE_ELEMENT_NAME = "item" |
169 | DICT_ITEM_NAME = "qqDictItem" | |
170 | DICT_ITEM_KEY_NAME = "key" | |
171 | DICT_ITEM_VALUE_NAME = "value" | |
bbf7159c | 172 | |
2eeaec19 | 173 | # This list doesn"t seem to be used. |
bbf7159c | 174 | # Internal documentation or useless? You make the call! |
2eeaec19 RD |
175 | ##MEMBERS_TO_SKIP = ("__module__", "__doc__", "__xmlname__", "__xmlattributes__", |
176 | ## "__xmlexclude__", "__xmlflattensequence__", "__xmlnamespaces__", | |
177 | ## "__xmldefaultnamespace__", "__xmlattrnamespaces__", | |
178 | ## "__xmlattrgroups__") | |
bbf7159c | 179 | |
1f780e48 RD |
180 | ################################################################################ |
181 | # | |
182 | # classes and functions | |
183 | # | |
184 | ################################################################################ | |
185 | ||
2eeaec19 RD |
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 | |
192 | return | |
193 | object.__dict__[name] = value | |
194 | ||
195 | def getComplexType(obj): | |
196 | if (hasattr(obj, "__xsdcomplextype__")): | |
197 | return obj.__xsdcomplextype__ | |
198 | return None | |
199 | ||
02b800ce | 200 | def _objectfactory(objname, objargs=None, objclass=None): |
2eeaec19 | 201 | "dynamically create an object based on the objname and return it." |
02b800ce | 202 | ## print "[objectfactory] objname [%s]" % (objname) |
6f1a3f9c RD |
203 | if not isinstance(objargs, list): |
204 | objargs = [objargs] | |
02b800ce RD |
205 | if (objclass != None): |
206 | if (len(objargs) > 0): | |
207 | if (hasattr(objclass, "__xmlcdatacontent__")): | |
208 | obj = objclass() | |
209 | contentAttr = obj.__xmlcdatacontent__ | |
210 | obj.__dict__[contentAttr] = str(objargs[0]) | |
211 | return obj | |
212 | return objclass(*objargs) | |
1f780e48 | 213 | else: |
02b800ce RD |
214 | return objclass() |
215 | return objutils.newInstance(objname, objargs) | |
1f780e48 RD |
216 | |
217 | class Element: | |
02b800ce | 218 | def __init__(self, name, attrs=None, xsname=None): |
1f780e48 RD |
219 | self.name = name |
220 | self.attrs = attrs | |
2eeaec19 | 221 | self.content = "" |
1f780e48 | 222 | self.children = [] |
02b800ce RD |
223 | self.objclass = None |
224 | self.xsname = xsname | |
2eeaec19 | 225 | |
1f780e48 | 226 | def getobjtype(self): |
2eeaec19 RD |
227 | objtype = self.attrs.get("objtype") |
228 | if (objtype == None): | |
229 | if (len(self.children) > 0): | |
230 | objtype = "dict" | |
231 | else: | |
232 | objtype = "str" | |
233 | return objtype | |
234 | ||
02b800ce RD |
235 | class NsElement(object): |
236 | def __init__(self): | |
237 | self.nsMap = {} | |
238 | self.targetNS = None | |
239 | self.defaultNS = None | |
240 | self.prefix = None | |
241 | ||
242 | def isEmpty(self): | |
243 | return ((self.nsMap == {}) and (self.targetNS == None) and (self.defaultNS == None)) | |
244 | ||
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() | |
252 | for tag in newKT: | |
253 | if tag.find(':') < 0: | |
254 | del self.knownTypes[tag] | |
255 | newMap = parentNSE.nsMap.copy() | |
256 | if self.nsMap != {}: | |
257 | for k, v in self.nsMap.iteritems(): | |
258 | newMap[k] = v | |
259 | self.nsMap = newMap | |
260 | else: | |
261 | self.knownTypes = {} | |
262 | reversedKNS = {} | |
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(): | |
270 | i = tag.rfind(':') | |
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 | |
281 | break | |
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__) | |
287 | ||
288 | def expandQName(self, eName, attrName, attrValue): | |
289 | bigValue = attrValue | |
290 | i = attrValue.rfind(':') | |
291 | if (i < 0): | |
292 | if self.defaultNS != None: | |
293 | bigValue = '%s:%s' % (self.defaultNS, attrValue) | |
294 | else: | |
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) | |
300 | break | |
301 | ## print '[expandQName] input attrName = "%s" and attrValue "%s"; output = "%s"' % (attrName, attrValue, bigValue) | |
302 | return bigValue | |
1f780e48 RD |
303 | |
304 | class XMLObjectFactory(xml.sax.ContentHandler): | |
02b800ce | 305 | def __init__(self, knownTypes=None, knownNamespaces=None): |
1f780e48 | 306 | self.rootelement = None |
02b800ce RD |
307 | if (knownTypes == None): |
308 | self.knownTypes = {} | |
309 | else: | |
310 | self.knownTypes = knownTypes | |
311 | if (knownNamespaces == None): | |
312 | self.knownNamespaces = {} | |
313 | else: | |
314 | self.knownNamespaces = knownNamespaces | |
315 | self.skipper = False | |
1f780e48 | 316 | self.elementstack = [] |
02b800ce RD |
317 | self.nsstack = [] |
318 | self.collectContent = None | |
1f780e48 RD |
319 | xml.sax.handler.ContentHandler.__init__(self) |
320 | ||
02b800ce RD |
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] | |
326 | else: | |
327 | newNS.knownTypes = self.knownTypes.copy() | |
bbf7159c | 328 | else: |
02b800ce RD |
329 | if (len(self.nsstack) > 0): |
330 | newNS.setKnownTypes(self.knownTypes, self.knownNamespaces, self.nsstack[-1]) | |
331 | else: | |
332 | newNS.setKnownTypes(self.knownTypes, self.knownNamespaces, None) | |
333 | self.nsstack.append(newNS) | |
334 | return newNS | |
335 | ||
336 | def popElementStack(self): | |
337 | element = self.elementstack.pop() | |
338 | nse = self.nsstack.pop() | |
339 | return element, nse | |
bbf7159c | 340 | |
1f780e48 RD |
341 | ## ContentHandler methods |
342 | def startElement(self, name, attrs): | |
02b800ce RD |
343 | ## print '[startElement] <%s>' % (name) |
344 | if name == 'xs:annotation' or name == 'xsd:annotation': # should use namespace mapping here | |
345 | self.skipper = True | |
346 | self.appendElementStack(Element(name, attrs.copy()), NsElement()) | |
347 | if self.skipper: | |
348 | return | |
349 | if self.collectContent != None: | |
350 | strVal = '<%s' % (name) | |
351 | for aKey, aVal in attrs.items(): | |
352 | strVal += (' %s="%s"' % (aKey, aVal)) | |
353 | strVal += '>' | |
354 | self.collectContent.content += strVal | |
355 | xsname = name | |
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 | |
360 | nse = NsElement() | |
361 | for k in attrs.getNames(): | |
362 | if k.startswith('xmlns'): | |
363 | longNs = attrs[k] | |
364 | eLongNs = longNs + '/' | |
365 | if str(eLongNs) in asDict(self.knownNamespaces): | |
366 | longNs = eLongNs | |
367 | if k == 'xmlns': | |
368 | nse.defaultNS = longNs | |
369 | else: | |
370 | shortNs = k[6:] | |
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 | |
1f780e48 RD |
378 | |
379 | def characters(self, content): | |
02b800ce | 380 | ## print '[characters] "%s" (%s)' % (content, type(content)) |
2eeaec19 | 381 | if (content != None): |
02b800ce RD |
382 | if self.collectContent != None: |
383 | self.collectContent.content += content | |
384 | else: | |
385 | self.elementstack[-1].content += content | |
1f780e48 RD |
386 | |
387 | def endElement(self, name): | |
02b800ce | 388 | ## print "[endElement] </%s>" % name |
1f780e48 | 389 | xsname = name |
2eeaec19 RD |
390 | if name.find(":") > -1: # Strip namespace prefixes for now until actually looking them up in xsd |
391 | name = name[name.find(":") + 1:] | |
02b800ce RD |
392 | if self.skipper: |
393 | if xsname == "xs:annotation" or xsname == "xsd:annotation": # here too | |
394 | self.skipper = False | |
395 | self.popElementStack() | |
396 | return | |
397 | if self.collectContent != None: | |
398 | if xsname != self.collectContent.xsname: | |
399 | self.collectContent.content += ('</%s>' % (xsname)) | |
400 | self.popElementStack() | |
401 | return | |
402 | else: | |
403 | self.collectContent = None | |
bbf7159c | 404 | oldChildren = self.elementstack[-1].children |
02b800ce | 405 | element, nse = self.popElementStack() |
bbf7159c RD |
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] | |
1f780e48 | 411 | objtype = element.getobjtype() |
bbf7159c RD |
412 | ## print "element objtype is: ", objtype |
413 | if (objtype == "None"): | |
414 | ## print "[endElement] %s: skipping a (objtype==None) end tag" % name | |
415 | return | |
1f780e48 | 416 | constructorarglist = [] |
2eeaec19 | 417 | if (len(element.content) > 0): |
1f780e48 | 418 | strippedElementContent = element.content.strip() |
2eeaec19 | 419 | if (len(strippedElementContent) > 0): |
1f780e48 | 420 | constructorarglist.append(element.content) |
02b800ce | 421 | obj = _objectfactory(objtype, constructorarglist, element.objclass) |
2eeaec19 RD |
422 | complexType = getComplexType(obj) |
423 | if (obj != None): | |
424 | if (hasattr(obj, "__xmlname__") and getattr(obj, "__xmlname__") == "sequence"): | |
bbf7159c | 425 | self.elementstack[-1].children = oldChildren |
bbf7159c | 426 | return |
2eeaec19 | 427 | if (len(element.attrs) > 0) and not isinstance(obj, list): |
bbf7159c | 428 | ## print "[endElement] %s: element has attrs and the obj is not a list" % name |
1f780e48 RD |
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:] | |
433 | else: | |
434 | ns = "" | |
02b800ce RD |
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 | |
2eeaec19 RD |
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): | |
1f780e48 | 447 | xsdElement = complexType.findElement(attrname) |
2eeaec19 | 448 | if (xsdElement != None): |
1f780e48 | 449 | type = xsdElement.type |
02b800ce | 450 | ## print 'Unmarshalling element "%s", attribute "%s" with type "%s"' % (name, xsdElement.name, type) |
2eeaec19 | 451 | if (type != None): |
02b800ce RD |
452 | if (type == TYPE_QNAME): |
453 | attr = nse.expandQName(name, attrname, attr) | |
2eeaec19 | 454 | type = xsdToLangType(type) |
1f780e48 RD |
455 | ### ToDO remove maxOccurs hack after bug 177 is fixed |
456 | if attrname == "maxOccurs" and attr == "unbounded": | |
457 | attr = "-1" | |
458 | attr = _objectfactory(type, attr) | |
2eeaec19 RD |
459 | try: |
460 | setattrignorecase(obj, _toAttrName(obj, attrname), attr) | |
461 | except AttributeError: | |
02b800ce RD |
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) | |
1f780e48 RD |
464 | ## obj.__dict__[_toAttrName(obj, attrname)] = attr |
465 | # stuff any child attributes meant to be in a sequence via the __xmlflattensequence__ | |
466 | flattenDict = {} | |
2eeaec19 RD |
467 | if hasattr(obj, "__xmlflattensequence__"): |
468 | flatten = obj.__xmlflattensequence__ | |
2eeaec19 | 469 | if (isinstance(flatten, dict)): |
2eeaec19 RD |
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 | |
475 | else: | |
476 | for xmlname in xmlnametuple: | |
02b800ce | 477 | ## print "[endElement]: adding flattenDict[%s] = %s" % (xmlname, sequencename) |
2eeaec19 | 478 | flattenDict[xmlname] = sequencename |
bbf7159c | 479 | else: |
02b800ce | 480 | raise Exception("Invalid type for __xmlflattensequence___ : it must be a dict") |
1f780e48 | 481 | |
2eeaec19 | 482 | # reattach an object"s attributes to it |
1f780e48 | 483 | for childname, child in element.children: |
bbf7159c | 484 | ## print "[endElement] childname is: ", childname, "; child is: ", child |
2eeaec19 | 485 | if (childname in flattenDict): |
1f780e48 | 486 | sequencename = _toAttrName(obj, flattenDict[childname]) |
2eeaec19 | 487 | if (not hasattr(obj, sequencename)): |
2eeaec19 RD |
488 | obj.__dict__[sequencename] = [] |
489 | sequencevalue = getattr(obj, sequencename) | |
490 | if (sequencevalue == None): | |
491 | obj.__dict__[sequencename] = [] | |
492 | sequencevalue = getattr(obj, sequencename) | |
1f780e48 | 493 | sequencevalue.append(child) |
2eeaec19 | 494 | elif (objtype == "list"): |
1f780e48 | 495 | obj.append(child) |
2eeaec19 RD |
496 | elif isinstance(obj, dict): |
497 | if (childname == DICT_ITEM_NAME): | |
498 | obj[child[DICT_ITEM_KEY_NAME]] = child[DICT_ITEM_VALUE_NAME] | |
499 | else: | |
500 | obj[childname] = child | |
1f780e48 RD |
501 | else: |
502 | ## print "childname = %s, obj = %s, child = %s" % (childname, repr(obj), repr(child)) | |
2eeaec19 RD |
503 | try: |
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 | |
1f780e48 | 508 | |
2eeaec19 | 509 | if (complexType != None): |
1f780e48 RD |
510 | for element in complexType.elements: |
511 | if element.default: | |
512 | elementName = _toAttrName(obj, element.name) | |
513 | if ((elementName not in obj.__dict__) or (obj.__dict__[elementName] == None)): | |
2eeaec19 RD |
514 | langType = xsdToLangType(element.type) |
515 | defaultValue = _objectfactory(langType, element.default) | |
1f780e48 RD |
516 | obj.__dict__[elementName] = defaultValue |
517 | ||
2eeaec19 RD |
518 | ifDefPy() |
519 | if (isinstance(obj, list)): | |
520 | if ((element.attrs.has_key("mutable")) and (element.attrs.getValue("mutable") == "false")): | |
521 | obj = tuple(obj) | |
522 | endIfDef() | |
523 | ||
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" | |
528 | else: | |
529 | self.rootelement = obj | |
530 | ||
1f780e48 RD |
531 | def getRootObject(self): |
532 | return self.rootelement | |
533 | ||
534 | def _toAttrName(obj, name): | |
535 | if (hasattr(obj, "__xmlrename__")): | |
536 | for key, val in obj.__xmlrename__.iteritems(): | |
537 | if (name == val): | |
538 | name = key | |
539 | break | |
540 | ## if (name.startswith("__") and not name.endswith("__")): | |
541 | ## name = "_%s%s" % (obj.__class__.__name__, name) | |
542 | return name | |
543 | ||
2eeaec19 | 544 | __typeMappingXsdToLang = { |
1f780e48 RD |
545 | "string": "str", |
546 | "char": "str", | |
547 | "varchar": "str", | |
2eeaec19 | 548 | "date": "str", # ToDO Need to work out how to create lang date types |
1f780e48 RD |
549 | "boolean": "bool", |
550 | "decimal": "float", # ToDO Does python have a better fixed point type? | |
551 | "int": "int", | |
02b800ce | 552 | "integer":"int", |
1f780e48 RD |
553 | "long": "long", |
554 | "float": "float", | |
555 | "bool": "bool", | |
556 | "str": "str", | |
557 | "unicode": "unicode", | |
bbf7159c RD |
558 | "short": "int", |
559 | "duration": "str", # see above (date) | |
560 | "datetime": "str", # see above (date) | |
561 | "time": "str", # see above (date) | |
562 | "double": "float", | |
02b800ce RD |
563 | "QName" : "str", |
564 | "blob" : "str", # ag:blob | |
565 | "currency" : "str", # ag:currency | |
1f780e48 RD |
566 | } |
567 | ||
2eeaec19 | 568 | def xsdToLangType(xsdType): |
02b800ce RD |
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:] | |
2eeaec19 RD |
573 | langType = __typeMappingXsdToLang.get(xsdType) |
574 | if (langType == None): | |
1f780e48 | 575 | raise Exception("Unknown xsd type %s" % xsdType) |
2eeaec19 | 576 | return langType |
02b800ce RD |
577 | |
578 | def langToXsdType(langType): | |
579 | if langType in asDict(__typeMappingXsdToLang): | |
580 | return '%s:%s' % (XMLSCHEMA_XSD_URL, langType) | |
581 | return langType | |
1f780e48 | 582 | |
2eeaec19 RD |
583 | def _getXmlValue(langValue): |
584 | if (isinstance(langValue, bool)): | |
585 | return str(langValue).lower() | |
586 | elif (isinstance(langValue, unicode)): | |
587 | return langValue.encode() | |
1f780e48 | 588 | else: |
2eeaec19 | 589 | return str(langValue) |
1f780e48 | 590 | |
02b800ce RD |
591 | def unmarshal(xmlstr, knownTypes=None, knownNamespaces=None, xmlSource=None): |
592 | objectfactory = XMLObjectFactory(knownTypes, knownNamespaces) | |
593 | try: | |
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) | |
1f780e48 RD |
600 | return objectfactory.getRootObject() |
601 | ||
02b800ce RD |
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)) | |
2eeaec19 RD |
608 | if (isinstance(encoding, basestring)): |
609 | return '<?xml version="1.0" encoding="%s"?>\n%s' % (encoding, xmlstr.encode(encoding)) | |
610 | elif (encoding == None): | |
6f1a3f9c | 611 | return xmlstr |
2eeaec19 RD |
612 | else: |
613 | return '<?xml version="1.0" encoding="%s"?>\n%s' % (sys.getdefaultencoding(), xmlstr) | |
6f1a3f9c | 614 | |
02b800ce RD |
615 | class XMLMarshalWorker(object): |
616 | def __init__(self, marshalType=True, prettyPrint=False, knownTypes=None, knownNamespaces=None): | |
617 | if knownTypes == None: | |
618 | self.knownTypes = {} | |
1f780e48 | 619 | else: |
02b800ce RD |
620 | self.knownTypes = knownTypes |
621 | if knownNamespaces == None: | |
622 | self.knownNamespaces = {} | |
623 | else: | |
624 | self.knownNamespaces = knownNamespaces | |
625 | self.prettyPrint = prettyPrint | |
626 | self.marshalType = marshalType | |
627 | self.xmldeepexclude = [] | |
628 | self.nsstack = [] | |
629 | ||
630 | def getNSPrefix(self): | |
631 | if len(self.nsstack) > 0: | |
632 | return self.nsstack[-1].prefix | |
633 | return '' | |
634 | ||
635 | def isKnownType(self, elementName): | |
636 | tagLongNs = None | |
637 | nse = self.nsstack[-1] | |
638 | i = elementName.rfind(':') | |
639 | if i > 0: | |
640 | prefix = elementName[:i] | |
641 | name = elementName[i+1:] | |
642 | else: | |
643 | prefix = DEFAULT_NAMESPACE_KEY | |
644 | name = elementName | |
645 | for shortNs, longNs in nse.nameSpaces.iteritems(): | |
646 | if shortNs == prefix: | |
647 | tagLongNs = longNs | |
648 | break | |
649 | if tagLongNs == None: | |
650 | knownTagName = elementName | |
651 | else: | |
652 | knownShortNs = self.knownNamespaces[tagLongNs] | |
653 | knownTagName = knownShortNs + ':' + name | |
654 | if (knownTagName in asDict(self.knownTypes)): | |
655 | knownClass = self.knownTypes[knownTagName] | |
656 | return True | |
657 | return False | |
658 | ||
659 | def popNSStack(self): | |
660 | self.nsstack.pop() | |
661 | ||
662 | def appendNSStack(self, obj): | |
663 | nameSpaces = {} | |
664 | defaultLongNS = None | |
665 | for nse in self.nsstack: | |
666 | for k, v in nse.nsMap.iteritems(): | |
667 | nameSpaces[k] = v | |
668 | if k == DEFAULT_NAMESPACE_KEY: | |
669 | defaultLongNS = v | |
670 | newNS = NsElement() | |
671 | nameSpaceAttrs = "" | |
672 | if hasattr(obj, "__xmlnamespaces__"): | |
673 | ns = getattr(obj, "__xmlnamespaces__") | |
674 | keys = ns.keys() | |
675 | keys.sort() | |
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: | |
681 | nameSpaceKey = k | |
682 | break | |
2eeaec19 | 683 | else: |
02b800ce RD |
684 | if nameSpaceKey == "": |
685 | defaultLongNS = nameSpaceUrl | |
686 | nameSpaces[DEFAULT_NAMESPACE_KEY] = nameSpaceUrl | |
687 | newNS.nsMap[DEFAULT_NAMESPACE_KEY] = nameSpaceUrl | |
688 | nameSpaceAttrs += ' xmlns="%s" ' % (nameSpaceUrl) | |
689 | else: | |
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 | |
2eeaec19 | 696 | else: |
02b800ce RD |
697 | newNS.prefix = '' |
698 | if hasattr(obj, "__xmldefaultnamespace__"): | |
699 | longPrefixNS = getattr(obj, "__xmldefaultnamespace__") | |
700 | if longPrefixNS == defaultLongNS: | |
701 | newNS.prefix = '' | |
702 | else: | |
703 | try: | |
704 | for k, v in nameSpaces.iteritems(): | |
705 | if v == longPrefixNS: | |
706 | newNS.prefix = k + ':' | |
707 | break; | |
708 | ## print '[appendNSStack] found longPrefixNS in nameSpaces = "%s"' % (newNS.prefix) | |
709 | except: | |
710 | if (longPrefixNS in asDict(self.knownNamespaces)): | |
711 | newNS.prefix = self.knownNamespaces[longPrefixNS] + ':' | |
712 | else: | |
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 | |
721 | ||
722 | def contractQName(self, value, obj, attr): | |
723 | value = langToXsdType(value) | |
724 | i = value.rfind(':') | |
725 | if i >= 0: | |
726 | longNS = value[:i] | |
2eeaec19 | 727 | else: |
02b800ce RD |
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) | |
731 | return value | |
732 | if (longNS in self.nsstack[-1].nameSpaces.values()): | |
733 | for kShort, vLong in self.nsstack[-1].nameSpaces.iteritems(): | |
734 | if vLong == longNS: | |
735 | shortNS = kShort | |
736 | break | |
737 | else: | |
738 | shortNS = longNS # if we can't find the long->short mappping, just use longNS | |
739 | if shortNS == DEFAULT_NAMESPACE_KEY: | |
740 | value = value[i+1:] | |
1f780e48 | 741 | else: |
02b800ce RD |
742 | value = shortNS + ':' + value[i+1:] |
743 | return value | |
744 | ||
745 | def _genObjTypeStr(self, typeString): | |
746 | if self.marshalType: | |
747 | return ' objtype="%s"' % typeString | |
748 | return "" | |
bbf7159c | 749 | |
02b800ce RD |
750 | def _marshal(self, obj, elementName=None, nameSpacePrefix="", indent=0): |
751 | if (obj != None): | |
752 | xmlMarshallerLogger.debug("--> _marshal: elementName=%s%s, type=%s, obj=%s, indent=%d" % (nameSpacePrefix, elementName, type(obj), str(obj), indent)) | |
753 | else: | |
754 | xmlMarshallerLogger.debug("--> _marshal: elementName=%s%s, obj is None, indent=%d" % (nameSpacePrefix, elementName, indent)) | |
755 | excludeAttrs = [] | |
756 | excludeAttrs.extend(self.xmldeepexclude) | |
757 | if hasattr(obj, "__xmlexclude__"): | |
758 | excludeAttrs.extend(obj.__xmlexclude__) | |
759 | prettyPrint = self.prettyPrint | |
760 | knownTypes = self.knownTypes | |
761 | xmlString = None | |
762 | if self.prettyPrint or indent: | |
763 | prefix = " "*indent | |
764 | newline = "\n" | |
765 | increment = 2 | |
1f780e48 | 766 | else: |
02b800ce RD |
767 | prefix = "" |
768 | newline = "" | |
769 | increment = 0 | |
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() | |
775 | if not elementName: | |
776 | if hasattr(obj, "__xmlname__"): | |
777 | elementName = nameSpacePrefix + obj.__xmlname__ | |
bbf7159c | 778 | else: |
02b800ce RD |
779 | elementName = nameSpacePrefix + BASETYPE_ELEMENT_NAME |
780 | else: | |
781 | elementName = nameSpacePrefix + elementName | |
782 | ## print '[XMLMarshalWorker._marshal] elementName "%s"; nameSpaceAttrs is "%s"' % (elementName, nameSpaceAttrs) | |
783 | ||
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 + ':' | |
789 | break | |
790 | else: | |
791 | xsdPrefix = 'xs:' | |
792 | elementAdd = xsdPrefix + obj.__xmlsequencer__ | |
793 | else: | |
794 | elementAdd = None | |
795 | ||
796 | ## print "marshal: entered with elementName: ", elementName | |
797 | members_to_skip = [] | |
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. | |
802 | objattrs = "" | |
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 | |
811 | ifDefPy() | |
812 | if (attr.startswith("__") and not attr.endswith("__")): | |
813 | internalAttrName = classNamePrefix + attr | |
814 | endIfDef() | |
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 | |
822 | continue | |
823 | if attr in nameSpaceAttributes: | |
824 | attrNameSpacePrefix = nameSpaceKey + ":" | |
825 | break | |
826 | attrs = obj.__dict__ | |
827 | value = attrs.get(internalAttrName) | |
828 | if (hasattr(obj, "__xmlrename__") and attr in asDict(obj.__xmlrename__)): | |
829 | attr = obj.__xmlrename__[attr] | |
830 | xsdElement = None | |
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))): | |
838 | continue | |
1f780e48 | 839 | else: |
02b800ce RD |
840 | if (value == None): |
841 | continue | |
842 | elif xsdElement.type == TYPE_QNAME: | |
843 | value = self.contractQName(value, obj, attr) | |
844 | elif value == None: | |
845 | continue | |
846 | ||
847 | # ToDO remove maxOccurs hack after bug 177 is fixed | |
848 | if attr == "maxOccurs" and value == -1: | |
849 | value = "unbounded" | |
850 | ||
851 | if isinstance(value, bool): | |
852 | if value == True: | |
853 | value = "true" | |
854 | else: | |
855 | value = "false" | |
856 | else: | |
857 | value = objutils.toDiffableRepr(value) | |
858 | ||
859 | objattrs += ' %s%s="%s"' % (attrNameSpacePrefix, attr, saxutils.escape(value)) | |
860 | ## print "marshal: new objattrs is: ", objattrs | |
861 | if (obj == None): | |
862 | xmlString = [""] | |
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): | |
880 | if len(obj) < 1: | |
881 | xmlString = "" | |
882 | else: | |
883 | objTypeStr = self._genObjTypeStr("list") | |
884 | xmlString = ['%s<%s%s>%s' % (prefix, elementName, objTypeStr, newline)] | |
885 | for item in obj: | |
886 | xmlString.extend(self._marshal(item, indent=indent+increment)) | |
887 | xmlString.append("%s</%s>%s" % (prefix, elementName, newline)) | |
888 | elif isinstance(obj, tuple): | |
889 | if len(obj) < 1: | |
890 | xmlString = "" | |
1f780e48 | 891 | else: |
02b800ce RD |
892 | objTypeStr = self._genObjTypeStr("list") |
893 | xmlString = ['%s<%s%s mutable="false">%s' % (prefix, elementName, objTypeStr, newline)] | |
894 | for item in obj: | |
895 | xmlString.extend(self._marshal(item, indent=indent+increment)) | |
2eeaec19 | 896 | xmlString.append("%s</%s>%s" % (prefix, elementName, newline)) |
02b800ce RD |
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 | |
902 | keys = obj.keys() | |
903 | keys.sort() | |
904 | for key in keys: | |
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: | |
913 | contentValue = '' | |
914 | else: | |
915 | contentValue = saxutils.escape(contentValue) | |
916 | xmlString = ["%s<%s%s%s>%s</%s>%s" % (prefix, elementName, nameSpaceAttrs, objattrs, contentValue, elementName, newline)] | |
1f780e48 | 917 | else: |
02b800ce RD |
918 | # Only add the objtype if the element tag is unknown to us. |
919 | if (self.isKnownType(elementName) == True): | |
920 | objTypeStr = "" | |
921 | else: | |
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*" " | |
927 | indent += increment | |
928 | xmlMemberString = [] | |
929 | if hasattr(obj, "__xmlbody__"): | |
930 | xmlbody = getattr(obj, obj.__xmlbody__) | |
931 | if xmlbody != None: | |
932 | xmlMemberString.append(xmlbody) | |
933 | else: | |
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]) | |
940 | else: | |
941 | attrGroups = {} | |
942 | # add the list of all attributes to attrGroups | |
943 | eList = obj.__dict__.keys() | |
944 | eList.sort() | |
945 | attrGroups["__nogroup__"] = eList | |
946 | ||
947 | for eName, eList in attrGroups.iteritems(): | |
948 | if (eName != "__nogroup__"): | |
949 | prefix += increment*" " | |
950 | indent += increment | |
951 | objTypeStr = self._genObjTypeStr("None") | |
952 | xmlMemberString.append('%s<%s%s>%s' % (prefix, eName, objTypeStr, newline)) | |
953 | for name in eList: | |
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)): | |
958 | continue | |
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 + ":" | |
964 | break | |
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)] | |
975 | xmlname = None | |
976 | if (len(xmlnametuple) == 1): | |
977 | xmlname = xmlnametuple[0] | |
978 | ## ix = 0 | |
979 | if not isinstance(value, (list, tuple)): | |
980 | value = [value] | |
981 | for seqitem in value: | |
982 | ## xmlname = xmlnametuple[ix] | |
983 | ## ix += 1 | |
984 | ## if (ix >= len(xmlnametuple)): | |
985 | ## ix = 0 | |
986 | xmlMemberString.extend(self._marshal(seqitem, xmlname, subElementNameSpacePrefix, indent=indent+increment)) | |
987 | else: | |
988 | if (hasattr(obj, "__xmlrename__") and name in asDict(obj.__xmlrename__)): | |
989 | xmlname = obj.__xmlrename__[name] | |
990 | else: | |
991 | xmlname = 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] | |
996 | indent -= increment | |
997 | ||
998 | # if we have nested elements, add them here, otherwise close the element tag immediately. | |
999 | newList = [] | |
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)) | |
1008 | else: | |
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] | |
1016 | indent -= increment | |
1017 | xmlString.append("%s</%s>%s" % (prefix, elementName, newline)) | |
1018 | else: | |
1019 | if hasattr(obj, "__xmlcdatacontent__"): | |
1020 | cdataAttr = obj.__xmlcdatacontent__ | |
1021 | cdataContent = obj.__dict__[cdataAttr] | |
1022 | xmlString.append("><![CDATA[%s]]></%s>%s" % (cdataContent, elementName, newline)) | |
1023 | else: | |
1024 | xmlString.append("/>%s" % newline) | |
1025 | ## return xmlString | |
1026 | xmlMarshallerLogger.debug("<-- _marshal: %s" % str(xmlString)) | |
1027 | #print "<-- _marshal: %s" % str(xmlString) | |
1028 | self.popNSStack() | |
1029 | return xmlString | |
2eeaec19 RD |
1030 | |
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"]} | |
1039 | ||
1040 | def setPerson(self): | |
1041 | self.firstName = "Albert" | |
1042 | self.lastName = "Camus" | |
1043 | self.addressLine1 = "23 Absurd St." | |
1044 | self.city = "Ennui" | |
1045 | self.state = "MO" | |
1046 | self.zip = "54321" | |
1047 | self._phoneNumber = "808-303-2323" | |
1048 | self.favoriteWords = ["angst", "ennui", "existence"] | |
1049 | self.phobias = ["war", "tuberculosis", "cars"] | |
1050 | self.weight = 150 | |
1051 | self.fabulousness = "tres tres" | |
1052 | self.nonSmoker = False | |
1053 | ||
1054 | if isMain(__name__): | |
1055 | p1 = MarshallerPerson() | |
1056 | p1.setPerson() | |
1057 | xmlP1 = marshal(p1, prettyPrint=True, encoding="utf-8") | |
1058 | print "\n########################" | |
1059 | print "# testPerson test case #" | |
1060 | print "########################" | |
1061 | print xmlP1 | |
1062 | p2 = unmarshal(xmlP1) | |
1063 | xmlP2 = marshal(p2, prettyPrint=True, encoding="utf-8") | |
1064 | if xmlP1 == xmlP2: | |
1065 | print "Success: repeated marshalling yields identical results" | |
1066 | else: | |
1067 | print "Failure: repeated marshalling yields different results" | |
1068 | print xmlP2 |