]>
Commit | Line | Data |
---|---|---|
1f780e48 RD |
1 | #---------------------------------------------------------------------------- |
2 | # Name: xmlmarshaller.py | |
3 | # Purpose: | |
4 | # | |
5 | # Author: John Spurling | |
6 | # | |
7 | # Created: 7/28/04 | |
8 | # CVS-ID: $Id$ | |
9 | # Copyright: (c) 2004-2005 ActiveGrid, Inc. | |
10 | # License: wxWindows License | |
11 | #---------------------------------------------------------------------------- | |
6f1a3f9c RD |
12 | import __builtin__ |
13 | import sys | |
1f780e48 RD |
14 | from types import * |
15 | import xml.sax | |
16 | import xml.sax.handler | |
1f780e48 RD |
17 | from xml.sax import saxutils |
18 | ||
6f1a3f9c RD |
19 | import objutils |
20 | ||
21 | MODULE_PATH = "__main__" | |
22 | ||
1f780e48 RD |
23 | ### ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed |
24 | ||
25 | """ | |
26 | ||
27 | More documentation later, but here are some special Python attributes | |
28 | that McLane recognizes: | |
29 | ||
30 | name: __xmlname__ | |
31 | type: string | |
32 | description: the name of the xml element for the marshalled object | |
33 | ||
34 | name: __xmlattributes__ | |
35 | type: tuple or list | |
36 | description: the name(s) of the Python string attribute(s) to be | |
37 | marshalled as xml attributes instead of nested xml elements. currently | |
38 | these can only be strings since there's not a way to get the type | |
39 | information back when unmarshalling. | |
40 | ||
41 | name: __xmlexclude__ | |
42 | type: tuple or list | |
43 | description: the name(s) of the python attribute(s) to skip when | |
44 | marshalling. | |
45 | ||
46 | name: __xmlrename__ | |
47 | type: dict | |
48 | description: describes an alternate Python <-> XML name mapping. | |
49 | Normally the name mapping is the identity function. __xmlrename__ | |
50 | overrides that. The keys are the Python names, the values are their | |
51 | associated XML names. | |
52 | ||
53 | name: __xmlflattensequence__ | |
54 | type: dict, tuple, or list | |
55 | description: the name(s) of the Python sequence attribute(s) whose | |
56 | items are to be marshalled as a series of xml elements (with an | |
57 | optional keyword argument that specifies the element name to use) as | |
58 | opposed to containing them in a separate sequence element, e.g.: | |
59 | ||
60 | myseq = (1, 2) | |
61 | <!-- normal way of marshalling --> | |
62 | <myseq> | |
63 | <item objtype='int'>1</item> | |
64 | <item objtype='int'>2</item> | |
65 | </myseq> | |
66 | <!-- with __xmlflattensequence__ set to {'myseq': 'squish'} --> | |
67 | <squish objtype='int'>1</squish> | |
68 | <squish objtype='int'>2</squish> | |
69 | ||
70 | name: __xmlnamespaces__ | |
71 | type: dict | |
72 | description: a dict of the namespaces that the object uses. Each item | |
73 | in the dict should consist of a prefix,url combination where the key is | |
74 | the prefix and url is the value, e.g.: | |
75 | ||
76 | __xmlnamespaces__ = { "xsd":"http://www.w3c.org/foo.xsd" } | |
77 | ||
78 | name: __xmldefaultnamespace__ | |
79 | type: String | |
80 | description: the prefix of a namespace defined in __xmlnamespaces__ that | |
81 | should be used as the default namespace for the object. | |
82 | ||
83 | name: __xmlattrnamespaces__ | |
84 | type: dict | |
85 | description: a dict assigning the Python object's attributes to the namespaces | |
86 | defined in __xmlnamespaces__. Each item in the dict should consist of a | |
87 | prefix,attributeList combination where the key is the prefix and the value is | |
88 | a list of the Python attribute names. e.g.: | |
89 | ||
90 | __xmlattrnamespaces__ = { "ag":["firstName", "lastName", "addressLine1", "city"] } | |
91 | ||
bbf7159c RD |
92 | name: __xmlattrgroups__ |
93 | type: dict | |
94 | description: a dict specifying groups of attributes to be wrapped in an enclosing tag. | |
95 | The key is the name of the enclosing tag; the value is a list of attributes to include | |
96 | within it. e.g. | |
97 | ||
98 | __xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]} | |
1f780e48 RD |
99 | |
100 | """ | |
101 | ||
102 | ################################################################################ | |
103 | # | |
104 | # module exceptions | |
105 | # | |
106 | ################################################################################ | |
107 | ||
108 | class Error(Exception): | |
109 | """Base class for errors in this module.""" | |
110 | pass | |
111 | ||
112 | class UnhandledTypeException(Error): | |
113 | """Exception raised when attempting to marshal an unsupported | |
114 | type. | |
115 | """ | |
116 | def __init__(self, typename): | |
117 | self.typename = typename | |
118 | def __str__(self): | |
119 | return "%s is not supported for marshalling." % str(self.typename) | |
120 | ||
121 | class XMLAttributeIsNotStringType(Error): | |
122 | """Exception raised when an object's attribute is specified to be | |
123 | marshalled as an XML attribute of the enclosing object instead of | |
124 | a nested element. | |
125 | """ | |
126 | def __init__(self, attrname, typename): | |
127 | self.attrname = attrname | |
128 | self.typename = typename | |
129 | def __str__(self): | |
130 | return """%s was set to be marshalled as an XML attribute | |
131 | instead of a nested element, but the object's type is %s, not | |
132 | string.""" % (self.attrname, self.typename) | |
133 | ||
134 | ################################################################################ | |
135 | # | |
136 | # constants and such | |
137 | # | |
138 | ################################################################################ | |
139 | ||
140 | XMLNS = 'xmlns' | |
141 | XMLNS_PREFIX = XMLNS + ':' | |
142 | XMLNS_PREFIX_LENGTH = len(XMLNS_PREFIX) | |
143 | ||
144 | BASETYPE_ELEMENT_NAME = 'item' | |
bbf7159c RD |
145 | |
146 | # This list doesn't seem to be used. | |
147 | # Internal documentation or useless? You make the call! | |
1f780e48 RD |
148 | MEMBERS_TO_SKIP = ('__module__', '__doc__', '__xmlname__', '__xmlattributes__', |
149 | '__xmlexclude__', '__xmlflattensequence__', '__xmlnamespaces__', | |
bbf7159c RD |
150 | '__xmldefaultnamespace__', '__xmlattrnamespaces__', |
151 | '__xmlattrgroups__') | |
152 | ||
1f780e48 RD |
153 | ################################################################################ |
154 | # | |
155 | # classes and functions | |
156 | # | |
157 | ################################################################################ | |
158 | ||
159 | def _objectfactory(objname, objargs=None, xsname=None): | |
6f1a3f9c RD |
160 | '''dynamically create an object based on the objname and return |
161 | it. look it up in the BASETYPE_ELEMENT_MAP first. | |
162 | ''' | |
163 | # split the objname into the typename and module path, | |
164 | # importing the module if need be. | |
165 | if not isinstance(objargs, list): | |
166 | objargs = [objargs] | |
1f780e48 | 167 | |
6f1a3f9c RD |
168 | if (xsname): |
169 | try: | |
170 | objname = knownGlobalTypes[xsname] | |
171 | except KeyError: | |
172 | pass | |
bbf7159c | 173 | |
6f1a3f9c RD |
174 | ## print "[objectfactory] creating an object of type %s and value %s, xsname=%s" % (objname, objargs, xsname) |
175 | objtype = objname.split('.')[-1] | |
176 | pathlist = objname.split('.') | |
177 | modulename = '.'.join(pathlist[0:-1]) | |
178 | ||
179 | ## print "[objectfactory] object [%s] %s(%r)" % (objname, objtype, objargs) | |
180 | if objname == 'bool': | |
181 | return not objargs[0].lower() == 'false' | |
182 | elif objname == 'str': # don't strip strings - blanks are significant !!! | |
183 | if len(objargs) > 0: | |
184 | return saxutils.unescape(objargs[0]).encode() | |
185 | else: | |
186 | return '' | |
187 | elif objname == 'unicode': # don't strip strings - blanks are significant !!! | |
188 | if len(objargs) > 0: | |
189 | return saxutils.unescape(objargs[0]).encode() | |
190 | else: | |
191 | return '' | |
192 | elif objtype in ('float', 'int', 'str', 'long'): | |
1f780e48 RD |
193 | objargs = [x.strip() for x in objargs] |
194 | ||
6f1a3f9c | 195 | try: |
1f780e48 RD |
196 | if __builtin__.__dict__.has_key(objname): |
197 | module = __builtin__ | |
26ee3a06 RD |
198 | elif knownGlobalModule: |
199 | module = knownGlobalModule | |
1f780e48 RD |
200 | else: |
201 | if modulename: | |
202 | module = __import__(modulename) | |
203 | for name in pathlist[1:-1]: | |
204 | module = module.__dict__[name] | |
205 | if objargs: | |
206 | return module.__dict__[objtype](*objargs) | |
207 | else: | |
208 | if objtype == 'None': | |
209 | return None | |
210 | return module.__dict__[objtype]() | |
211 | except KeyError: | |
212 | raise KeyError("Could not find class %s" % objname) | |
213 | ||
214 | class Element: | |
215 | def __init__(self, name, attrs=None): | |
216 | self.name = name | |
217 | self.attrs = attrs | |
218 | self.content = '' | |
219 | self.children = [] | |
220 | def getobjtype(self): | |
221 | if self.attrs.has_key('objtype'): | |
222 | return self.attrs.getValue('objtype') | |
223 | else: | |
224 | return 'str' | |
bbf7159c RD |
225 | def toString(self): |
226 | print " name = ", self.name, "; attrs = ", self.attrs, "number of children = ", len(self.children) | |
227 | i = -1 | |
228 | for child in self.children: | |
229 | i = i + 1 | |
230 | childClass = child.__class__.__name__ | |
231 | print " Child ", i, " class: ",childClass | |
1f780e48 RD |
232 | |
233 | ||
234 | class XMLObjectFactory(xml.sax.ContentHandler): | |
235 | def __init__(self): | |
236 | self.rootelement = None | |
237 | self.elementstack = [] | |
238 | xml.sax.handler.ContentHandler.__init__(self) | |
239 | ||
bbf7159c RD |
240 | def toString(self): |
241 | print "-----XMLObjectFactory Dump-------------------------------" | |
242 | if (self.rootelement == None): | |
243 | print "rootelement is None" | |
244 | else: | |
245 | print "rootelement is an object" | |
246 | i = -1 | |
247 | print "length of elementstack is: ", len(self.elementstack) | |
248 | for e in self.elementstack: | |
249 | i = i + 1 | |
250 | print "elementstack[", i, "]: " | |
251 | e.toString() | |
252 | print "-----end XMLObjectFactory--------------------------------" | |
253 | ||
1f780e48 RD |
254 | ## ContentHandler methods |
255 | def startElement(self, name, attrs): | |
bbf7159c | 256 | ## print "startElement for name: ", name |
1f780e48 RD |
257 | if name.find(':') > -1: # Strip namespace prefixes for now until actually looking them up in xsd |
258 | name = name[name.index(':') + 1:] | |
259 | ## for attrname in attrs.getNames(): | |
260 | ## print "%s: %s" % (attrname, attrs.getValue(attrname)) | |
261 | element = Element(name, attrs.copy()) | |
262 | self.elementstack.append(element) | |
263 | ## print self.elementstack | |
264 | ||
265 | def characters(self, content): | |
266 | ## print "got content: %s" % content | |
267 | if content: | |
268 | self.elementstack[-1].content += content | |
269 | ||
270 | def endElement(self, name): | |
271 | ## print "[endElement] name of element we're at the end of: %s" % name | |
272 | xsname = name | |
273 | if name.find(':') > -1: # Strip namespace prefixes for now until actually looking them up in xsd | |
274 | name = name[name.index(':') + 1:] | |
bbf7159c | 275 | oldChildren = self.elementstack[-1].children |
1f780e48 | 276 | element = self.elementstack.pop() |
bbf7159c RD |
277 | if ((len(self.elementstack) > 1) and (self.elementstack[-1].getobjtype() == "None")): |
278 | parentElement = self.elementstack[-2] | |
279 | ## print "[endElement] %s: found parent with objtype==None: using its grandparent" % name | |
280 | elif (len(self.elementstack) > 0): | |
281 | parentElement = self.elementstack[-1] | |
1f780e48 | 282 | objtype = element.getobjtype() |
bbf7159c RD |
283 | ## print "element objtype is: ", objtype |
284 | if (objtype == "None"): | |
285 | ## print "[endElement] %s: skipping a (objtype==None) end tag" % name | |
286 | return | |
1f780e48 RD |
287 | constructorarglist = [] |
288 | if element.content: | |
289 | strippedElementContent = element.content.strip() | |
290 | if strippedElementContent: | |
291 | constructorarglist.append(element.content) | |
bbf7159c | 292 | ## print "[endElement] calling objectfactory" |
1f780e48 RD |
293 | obj = _objectfactory(objtype, constructorarglist, xsname) |
294 | complexType = None | |
295 | if hasattr(obj, '__xsdcomplextype__'): | |
296 | complexType = getattr(obj, '__xsdcomplextype__') | |
bbf7159c RD |
297 | if (hasattr(obj, '__xmlname__') and getattr(obj, '__xmlname__') == "sequence"): |
298 | ## print "[endElement] sequence found" | |
299 | ## self.toString() | |
300 | self.elementstack[-1].children = oldChildren | |
301 | ## self.toString() | |
302 | ## print "done moving sequence stuff; returning" | |
303 | return | |
1f780e48 | 304 | if len(self.elementstack) > 0: |
bbf7159c RD |
305 | ## print "[endElement] appending child with name: ", name, "; objtype: ", objtype |
306 | parentElement.children.append((name, obj)) | |
307 | ## print "parentElement now has ", len(parentElement.children), " children" | |
1f780e48 RD |
308 | else: |
309 | self.rootelement = obj | |
310 | if element.attrs and not isinstance(obj, list): | |
bbf7159c | 311 | ## print "[endElement] %s: element has attrs and the obj is not a list" % name |
1f780e48 RD |
312 | for attrname, attr in element.attrs.items(): |
313 | if attrname == XMLNS or attrname.startswith(XMLNS_PREFIX): | |
314 | if attrname.startswith(XMLNS_PREFIX): | |
315 | ns = attrname[XMLNS_PREFIX_LENGTH:] | |
316 | else: | |
317 | ns = "" | |
318 | if not hasattr(obj, '__xmlnamespaces__'): | |
319 | obj.__xmlnamespaces__ = {ns:attr} | |
320 | elif ns not in obj.__xmlnamespaces__: | |
321 | if (hasattr(obj.__class__, '__xmlnamespaces__') | |
322 | and obj.__xmlnamespaces__ is obj.__class__.__xmlnamespaces__): | |
323 | obj.__xmlnamespaces__ = dict(obj.__xmlnamespaces__) | |
324 | obj.__xmlnamespaces__[ns] = attr | |
325 | elif not attrname == 'objtype': | |
326 | if attrname.find(':') > -1: # Strip namespace prefixes for now until actually looking them up in xsd | |
327 | attrname = attrname[attrname.index(':') + 1:] | |
328 | if complexType: | |
329 | xsdElement = complexType.findElement(attrname) | |
330 | if xsdElement: | |
331 | type = xsdElement.type | |
332 | if type: | |
333 | type = xsdToPythonType(type) | |
334 | ### ToDO remove maxOccurs hack after bug 177 is fixed | |
335 | if attrname == "maxOccurs" and attr == "unbounded": | |
336 | attr = "-1" | |
337 | attr = _objectfactory(type, attr) | |
6f1a3f9c | 338 | objutils.setattrignorecase(obj, _toAttrName(obj, attrname), attr) |
1f780e48 RD |
339 | ## obj.__dict__[_toAttrName(obj, attrname)] = attr |
340 | # stuff any child attributes meant to be in a sequence via the __xmlflattensequence__ | |
341 | flattenDict = {} | |
342 | if hasattr(obj, '__xmlflattensequence__'): | |
bbf7159c RD |
343 | ## print "[endElement] %s: obj has __xmlflattensequence__" % name |
344 | if (isinstance(obj.__xmlflattensequence__,dict)): | |
345 | ## print "[endElement] dict with obj.__xmlflattensequence__.items: ", obj.__xmlflattensequence__.items() | |
346 | for sequencename, xmlnametuple in obj.__xmlflattensequence__.items(): | |
347 | for xmlname in xmlnametuple: | |
348 | ## print "[endElement]: adding flattenDict[%s] = %s" % (xmlname, sequencename) | |
349 | flattenDict[xmlname] = sequencename | |
350 | # handle __xmlflattensequence__ list/tuple (i.e. no element rename) | |
351 | elif (isinstance(obj.__xmlflattensequence__,list) or isinstance(obj.__xmlflattensequence__,tuple)): | |
352 | for sequencename in obj.__xmlflattensequence__: | |
353 | flattenDict[sequencename] = sequencename | |
354 | else: | |
355 | raise "Invalid type for __xmlflattensequence___ : it must be a dict, list, or tuple" | |
1f780e48 RD |
356 | |
357 | # reattach an object's attributes to it | |
358 | for childname, child in element.children: | |
bbf7159c | 359 | ## print "[endElement] childname is: ", childname, "; child is: ", child |
1f780e48 RD |
360 | if flattenDict.has_key(childname): |
361 | sequencename = _toAttrName(obj, flattenDict[childname]) | |
bbf7159c | 362 | ## print "[endElement] sequencename is: ", sequencename |
1f780e48 | 363 | try: |
bbf7159c | 364 | ## print "[endElement] obj.__dict__ is: ", obj.__dict__ |
1f780e48 | 365 | sequencevalue = obj.__dict__[sequencename] |
6f1a3f9c | 366 | except (AttributeError, KeyError): |
bbf7159c | 367 | sequencevalue = None |
1f780e48 RD |
368 | if sequencevalue == None: |
369 | sequencevalue = [] | |
370 | obj.__dict__[sequencename] = sequencevalue | |
371 | sequencevalue.append(child) | |
372 | elif isinstance(obj, list): | |
bbf7159c | 373 | ## print "appended childname = ", childname |
1f780e48 RD |
374 | obj.append(child) |
375 | else: | |
376 | ## print "childname = %s, obj = %s, child = %s" % (childname, repr(obj), repr(child)) | |
6f1a3f9c | 377 | objutils.setattrignorecase(obj, _toAttrName(obj, childname), child) |
bbf7159c | 378 | obj.__dict__[_toAttrName(obj, childname)] = child |
1f780e48 RD |
379 | |
380 | if complexType: | |
381 | for element in complexType.elements: | |
382 | if element.default: | |
383 | elementName = _toAttrName(obj, element.name) | |
384 | if ((elementName not in obj.__dict__) or (obj.__dict__[elementName] == None)): | |
385 | pythonType = xsdToPythonType(element.type) | |
386 | defaultValue = _objectfactory(pythonType, element.default) | |
387 | obj.__dict__[elementName] = defaultValue | |
388 | ||
389 | def getRootObject(self): | |
390 | return self.rootelement | |
391 | ||
392 | def _toAttrName(obj, name): | |
393 | if (hasattr(obj, "__xmlrename__")): | |
394 | for key, val in obj.__xmlrename__.iteritems(): | |
395 | if (name == val): | |
396 | name = key | |
397 | break | |
398 | ## if (name.startswith("__") and not name.endswith("__")): | |
399 | ## name = "_%s%s" % (obj.__class__.__name__, name) | |
400 | return name | |
401 | ||
402 | __typeMappingXsdToPython = { | |
403 | "string": "str", | |
404 | "char": "str", | |
405 | "varchar": "str", | |
406 | "date": "str", # ToDO Need to work out how to create python date types | |
407 | "boolean": "bool", | |
408 | "decimal": "float", # ToDO Does python have a better fixed point type? | |
409 | "int": "int", | |
410 | "long": "long", | |
411 | "float": "float", | |
412 | "bool": "bool", | |
413 | "str": "str", | |
414 | "unicode": "unicode", | |
bbf7159c RD |
415 | "short": "int", |
416 | "duration": "str", # see above (date) | |
417 | "datetime": "str", # see above (date) | |
418 | "time": "str", # see above (date) | |
419 | "double": "float", | |
1f780e48 RD |
420 | } |
421 | ||
422 | def xsdToPythonType(xsdType): | |
423 | try: | |
424 | return __typeMappingXsdToPython[xsdType] | |
425 | except KeyError: | |
426 | raise Exception("Unknown xsd type %s" % xsdType) | |
427 | ||
428 | def _getXmlValue(pythonValue): | |
429 | if (isinstance(pythonValue, bool)): | |
430 | return str(pythonValue).lower() | |
6f1a3f9c RD |
431 | elif (isinstance(pythonValue, unicode)): |
432 | return pythonValue.encode() | |
1f780e48 RD |
433 | else: |
434 | return str(pythonValue) | |
435 | ||
26ee3a06 RD |
436 | def unmarshal(xmlstr, knownTypes=None, knownModule=None): |
437 | global knownGlobalTypes, knownGlobalModule | |
bbf7159c RD |
438 | if (knownTypes == None): |
439 | knownGlobalTypes = {} | |
440 | else: | |
441 | knownGlobalTypes = knownTypes | |
26ee3a06 | 442 | knownGlobalModule = knownModule |
1f780e48 RD |
443 | objectfactory = XMLObjectFactory() |
444 | xml.sax.parseString(xmlstr, objectfactory) | |
445 | return objectfactory.getRootObject() | |
446 | ||
447 | ||
6f1a3f9c RD |
448 | def marshal(obj, elementName=None, prettyPrint=False, indent=0, knownTypes=None, withEncoding=True, encoding=None): |
449 | xmlstr = ''.join(_marshal(obj, elementName, prettyPrint=prettyPrint, indent=indent, knownTypes=knownTypes)) | |
450 | if withEncoding: | |
451 | if encoding is None: | |
452 | return '<?xml version="1.0" encoding="%s"?>\n%s' % (sys.getdefaultencoding(), xmlstr) | |
453 | else: | |
454 | return '<?xml version="1.0" encoding="%s"?>\n%s' % (encoding, xmlstr.encode(encoding)) | |
455 | else: | |
456 | return xmlstr | |
457 | ||
458 | def _marshal(obj, elementName=None, nameSpacePrefix='', nameSpaces=None, prettyPrint=False, indent=0, knownTypes=None): | |
1f780e48 RD |
459 | if prettyPrint or indent: |
460 | prefix = ' '*indent | |
461 | newline = '\n' | |
462 | increment = 4 | |
463 | else: | |
464 | prefix = '' | |
465 | newline = '' | |
466 | increment = 0 | |
467 | ||
468 | ## Determine the XML element name. If it isn't specified in the | |
469 | ## parameter list, look for it in the __xmlname__ Python | |
470 | ## attribute, else use the default generic BASETYPE_ELEMENT_NAME. | |
471 | if not nameSpaces: nameSpaces = {} # Need to do this since if the {} is a default parameter it gets shared by all calls into the function | |
472 | nameSpaceAttrs = '' | |
bbf7159c RD |
473 | if knownTypes == None: |
474 | knownTypes = {} | |
1f780e48 RD |
475 | if hasattr(obj, '__xmlnamespaces__'): |
476 | for nameSpaceKey, nameSpaceUrl in getattr(obj, '__xmlnamespaces__').items(): | |
477 | if nameSpaceUrl in nameSpaces: | |
478 | nameSpaceKey = nameSpaces[nameSpaceUrl] | |
479 | else: | |
480 | ## # TODO: Wait to do this until there is shared state for use when going through the object graph | |
481 | ## origNameSpaceKey = nameSpaceKey # Make sure there is no key collision, ie: same key referencing two different URL's | |
482 | ## i = 1 | |
483 | ## while nameSpaceKey in nameSpaces.values(): | |
484 | ## nameSpaceKey = origNameSpaceKey + str(i) | |
485 | ## i += 1 | |
486 | nameSpaces[nameSpaceUrl] = nameSpaceKey | |
487 | if nameSpaceKey == '': | |
488 | nameSpaceAttrs += ' xmlns="%s" ' % (nameSpaceUrl) | |
489 | else: | |
490 | nameSpaceAttrs += ' xmlns:%s="%s" ' % (nameSpaceKey, nameSpaceUrl) | |
491 | nameSpaceAttrs = nameSpaceAttrs.rstrip() | |
492 | if hasattr(obj, '__xmldefaultnamespace__'): | |
bbf7159c | 493 | nameSpacePrefix = getattr(obj, '__xmldefaultnamespace__') + ':' |
1f780e48 RD |
494 | if not elementName: |
495 | if hasattr(obj, '__xmlname__'): | |
496 | elementName = nameSpacePrefix + obj.__xmlname__ | |
497 | else: | |
498 | elementName = nameSpacePrefix + BASETYPE_ELEMENT_NAME | |
499 | else: | |
500 | elementName = nameSpacePrefix + elementName | |
bbf7159c RD |
501 | if hasattr(obj, '__xmlsequencer__'): |
502 | elementAdd = obj.__xmlsequencer__ | |
503 | else: | |
504 | elementAdd = None | |
505 | ||
506 | ## print "marshal: entered with elementName: ", elementName | |
1f780e48 RD |
507 | members_to_skip = [] |
508 | ## Add more members_to_skip based on ones the user has selected | |
509 | ## via the __xmlexclude__ attribute. | |
510 | if hasattr(obj, '__xmlexclude__'): | |
bbf7159c | 511 | ## print "marshal: found __xmlexclude__" |
1f780e48 RD |
512 | members_to_skip += list(obj.__xmlexclude__) |
513 | # Marshal the attributes that are selected to be XML attributes. | |
514 | objattrs = '' | |
515 | className = obj.__class__.__name__ | |
516 | classNamePrefix = "_" + className | |
517 | if hasattr(obj, '__xmlattributes__'): | |
bbf7159c | 518 | ## print "marshal: found __xmlattributes__" |
1f780e48 RD |
519 | xmlattributes = obj.__xmlattributes__ |
520 | members_to_skip += xmlattributes | |
521 | for attr in xmlattributes: | |
522 | internalAttrName = attr | |
523 | if (attr.startswith("__") and not attr.endswith("__")): | |
524 | internalAttrName = classNamePrefix + attr | |
525 | # Fail silently if a python attribute is specified to be | |
526 | # an XML attribute but is missing. | |
bbf7159c | 527 | ## print "marshal: processing attribute ", internalAttrName |
1f780e48 RD |
528 | try: |
529 | value = obj.__dict__[internalAttrName] | |
530 | except KeyError: | |
531 | continue | |
532 | ## # But, check and see if it is a property first: | |
6f1a3f9c | 533 | ## if (objutils.hasPropertyValue(obj, attr)): |
1f780e48 RD |
534 | ## value = getattr(obj, attr) |
535 | ## else: | |
536 | ## continue | |
537 | xsdElement = None | |
538 | if hasattr(obj, '__xsdcomplextype__'): | |
bbf7159c | 539 | ## print "marshal: found __xsdcomplextype__" |
1f780e48 RD |
540 | complexType = getattr(obj, '__xsdcomplextype__') |
541 | xsdElement = complexType.findElement(attr) | |
542 | if xsdElement: | |
543 | default = xsdElement.default | |
544 | if default == value or default == _getXmlValue(value): | |
545 | continue | |
546 | elif value == None: | |
547 | continue | |
548 | ||
549 | # ToDO remove maxOccurs hack after bug 177 is fixed | |
550 | if attr == "maxOccurs" and value == -1: | |
551 | value = "unbounded" | |
552 | ||
553 | if isinstance(value, bool): | |
554 | if value == True: | |
555 | value = "true" | |
556 | else: | |
557 | value = "false" | |
558 | ||
559 | attrNameSpacePrefix = '' | |
560 | if hasattr(obj, '__xmlattrnamespaces__'): | |
bbf7159c | 561 | ## print "marshal: found __xmlattrnamespaces__" |
1f780e48 | 562 | for nameSpaceKey, nameSpaceAttributes in getattr(obj, '__xmlattrnamespaces__').items(): |
6f1a3f9c | 563 | if nameSpaceKey == nameSpacePrefix[:-1]: # Don't need to specify attribute namespace if it is the same as it's element |
1f780e48 RD |
564 | continue |
565 | if attr in nameSpaceAttributes: | |
566 | attrNameSpacePrefix = nameSpaceKey + ':' | |
567 | break | |
568 | ## if attr.startswith('_'): | |
569 | ## attr = attr[1:] | |
570 | if (hasattr(obj, "__xmlrename__") and attr in obj.__xmlrename__): | |
bbf7159c | 571 | ## print "marshal: found __xmlrename__ (and its attribute)" |
1f780e48 RD |
572 | attr = obj.__xmlrename__[attr] |
573 | ||
574 | objattrs += ' %s%s="%s"' % (attrNameSpacePrefix, attr, value) | |
bbf7159c | 575 | ## print "marshal: new objattrs is: ", objattrs |
1f780e48 | 576 | |
1f780e48 RD |
577 | if isinstance(obj, NoneType): |
578 | return '' | |
1f780e48 | 579 | elif isinstance(obj, bool): |
6f1a3f9c | 580 | return ['%s<%s objtype="bool">%s</%s>%s' % (prefix, elementName, obj, elementName, newline)] |
1f780e48 | 581 | elif isinstance(obj, int): |
6f1a3f9c | 582 | return ['''%s<%s objtype="int">%s</%s>%s''' % (prefix, elementName, str(obj), elementName, newline)] |
1f780e48 | 583 | elif isinstance(obj, long): |
6f1a3f9c | 584 | return ['%s<%s objtype="long">%s</%s>%s' % (prefix, elementName, str(obj), elementName, newline)] |
1f780e48 | 585 | elif isinstance(obj, float): |
6f1a3f9c RD |
586 | return ['%s<%s objtype="float">%s</%s>%s' % (prefix, elementName, str(obj), elementName, newline)] |
587 | elif isinstance(obj, unicode): # have to check before basestring - unicode is instance of base string | |
588 | return ['''%s<%s>%s</%s>%s''' % (prefix, elementName, saxutils.escape(obj.encode()), elementName, newline)] | |
1f780e48 | 589 | elif isinstance(obj, basestring): |
6f1a3f9c | 590 | return ['''%s<%s>%s</%s>%s''' % (prefix, elementName, saxutils.escape(obj), elementName, newline)] |
1f780e48 RD |
591 | elif isinstance(obj, list): |
592 | if len(obj) < 1: | |
593 | return '' | |
6f1a3f9c | 594 | xmlString = ['%s<%s objtype="list">%s' % (prefix, elementName, newline)] |
1f780e48 | 595 | for item in obj: |
6f1a3f9c RD |
596 | xmlString.extend(_marshal(item, nameSpaces=nameSpaces, indent=indent+increment, knownTypes=knownTypes)) |
597 | xmlString.append('%s</%s>%s' % (prefix, elementName, newline)) | |
1f780e48 RD |
598 | return xmlString |
599 | elif isinstance(obj, tuple): | |
600 | if len(obj) < 1: | |
601 | return '' | |
6f1a3f9c | 602 | xmlString = ['%s<%s objtype="list" mutable="false">%s' % (prefix, elementName, newline)] |
1f780e48 | 603 | for item in obj: |
6f1a3f9c RD |
604 | xmlString.extend(_marshal(item, nameSpaces=nameSpaces, indent=indent+increment, knownTypes=knownTypes)) |
605 | xmlString.append('%s</%s>%s' % (prefix, elementName, newline)) | |
1f780e48 RD |
606 | return xmlString |
607 | elif isinstance(obj, dict): | |
6f1a3f9c | 608 | xmlString = ['%s<%s objtype="dict">%s' % (prefix, elementName, newline)] |
1f780e48 RD |
609 | subprefix = prefix + ' '*increment |
610 | subindent = indent + 2*increment | |
611 | for key, val in obj.iteritems(): | |
6f1a3f9c RD |
612 | xmlString.append("%s<key>%s" % (subprefix, newline)) |
613 | xmlString.extend(_marshal(key, indent=subindent, knownTypes=knownTypes)) | |
614 | xmlString.append("%s</key>%s%s<value>%s" % (subprefix, newline, subprefix, newline)) | |
615 | xmlString.extend(_marshal(val, nameSpaces=nameSpaces, indent=subindent, knownTypes=knownTypes)) | |
616 | xmlString.append("%s</value>%s" % (subprefix, newline)) | |
617 | xmlString.append('%s</%s>%s' % (prefix, elementName, newline)) | |
1f780e48 RD |
618 | return xmlString |
619 | else: | |
620 | moduleName = obj.__class__.__module__ | |
621 | if (moduleName == "activegrid.model.schema"): | |
6f1a3f9c | 622 | xmlString = ['%s<%s%s%s' % (prefix, elementName, nameSpaceAttrs, objattrs)] |
1f780e48 | 623 | else: |
bbf7159c RD |
624 | # Only add the objtype if the element tag is unknown to us. |
625 | try: | |
626 | objname = knownTypes[elementName] | |
6f1a3f9c | 627 | xmlString = ['%s<%s%s%s' % (prefix, elementName, nameSpaceAttrs, objattrs)] |
bbf7159c | 628 | except KeyError: |
6f1a3f9c | 629 | xmlString = ['%s<%s%s%s objtype="%s.%s"' % (prefix, elementName, nameSpaceAttrs, objattrs, moduleName, className)] |
bbf7159c | 630 | ## print "UnknownTypeException: Unknown type (%s.%s) passed to marshaller" % (moduleName, className) |
6f1a3f9c | 631 | # get the member, value pairs for the object, filtering out the types we don't support |
bbf7159c RD |
632 | if (elementAdd != None): |
633 | prefix += increment*' ' | |
634 | indent += increment | |
635 | ||
6f1a3f9c | 636 | xmlMemberString = [] |
1f780e48 | 637 | if hasattr(obj, '__xmlbody__'): |
6f1a3f9c RD |
638 | xmlbody = getattr(obj, obj.__xmlbody__) |
639 | if xmlbody != None: | |
640 | xmlMemberString.append(xmlbody) | |
1f780e48 RD |
641 | else: |
642 | entryList = obj.__dict__.items() | |
643 | ## # Add in properties | |
644 | ## for key in obj.__class__.__dict__.iterkeys(): | |
645 | ## if (key not in members_to_skip and key not in obj.__dict__ | |
6f1a3f9c | 646 | ## and objutils.hasPropertyValue(obj, key)): |
1f780e48 RD |
647 | ## value = getattr(obj, key) |
648 | ## entryList.append((key, value)) | |
649 | entryList.sort() | |
bbf7159c RD |
650 | if hasattr(obj, '__xmlattrgroups__'): |
651 | attrGroups = obj.__xmlattrgroups__ | |
652 | if (not isinstance(attrGroups,dict)): | |
653 | raise "__xmlattrgroups__ is not a dict, but must be" | |
654 | for n in attrGroups: | |
655 | v = attrGroups[n] | |
656 | members_to_skip += v | |
657 | else: | |
658 | attrGroups = {} | |
659 | # add the list of all attributes to attrGroups | |
660 | eList = [] | |
661 | for x, z in entryList: | |
662 | eList.append(x) | |
663 | attrGroups['__nogroup__'] = eList | |
664 | ||
665 | for eName in attrGroups: | |
666 | eList = attrGroups[eName] | |
667 | if (eName != '__nogroup__'): | |
668 | prefix += increment*' ' | |
669 | indent += increment | |
6f1a3f9c | 670 | xmlMemberString.append('%s<%s objtype="None">%s' % (prefix, eName, newline)) |
bbf7159c RD |
671 | for name in eList: |
672 | value = obj.__dict__[name] | |
673 | ## print " ", name, " = ", value | |
674 | ## # special name handling for private "__*" attributes: | |
675 | ## # remove the _<class-name> added by Python | |
676 | ## if name.startswith(classNamePrefix): name = name[len(classNamePrefix):] | |
677 | if eName == '__nogroup__' and name in members_to_skip: continue | |
678 | if name.startswith('__') and name.endswith('__'): continue | |
679 | ## idx = name.find('__') | |
680 | ## if idx > 0: | |
681 | ## newName = name[idx+2:] | |
682 | ## if newName: | |
683 | ## name = newName | |
684 | ## print "marshal: processing subElement ", name | |
685 | subElementNameSpacePrefix = nameSpacePrefix | |
686 | if hasattr(obj, '__xmlattrnamespaces__'): | |
687 | for nameSpaceKey, nameSpaceValues in getattr(obj, '__xmlattrnamespaces__').items(): | |
688 | if name in nameSpaceValues: | |
689 | subElementNameSpacePrefix = nameSpaceKey + ':' | |
690 | break | |
691 | # handle sequences listed in __xmlflattensequence__ | |
692 | # specially: instead of listing the contained items inside | |
693 | # of a separate list, as god intended, list them inside | |
694 | # the object containing the sequence. | |
695 | if hasattr(obj, '__xmlflattensequence__') and name in obj.__xmlflattensequence__ and value: | |
696 | try: | |
697 | xmlnametuple = obj.__xmlflattensequence__[name] | |
698 | xmlname = None | |
699 | if len(xmlnametuple) == 1: | |
700 | xmlname = xmlnametuple[0] | |
701 | except: | |
702 | xmlname = name | |
703 | ## xmlname = name.lower() | |
704 | for seqitem in value: | |
6f1a3f9c | 705 | xmlMemberString.extend(_marshal(seqitem, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment, knownTypes=knownTypes)) |
1f780e48 | 706 | else: |
bbf7159c RD |
707 | if (hasattr(obj, "__xmlrename__") and name in obj.__xmlrename__): |
708 | xmlname = obj.__xmlrename__[name] | |
709 | else: | |
710 | xmlname = name | |
bbf7159c RD |
711 | ## if (indent > 30): |
712 | ## print "getting pretty deep, xmlname = ", xmlname | |
6f1a3f9c | 713 | xmlMemberString.extend(_marshal(value, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment, knownTypes=knownTypes)) |
bbf7159c RD |
714 | if (eName != '__nogroup__'): |
715 | ## print "marshal: Completing attrGroup ", eName | |
6f1a3f9c | 716 | xmlMemberString.append('%s</%s>%s' % (prefix, eName, newline)) |
bbf7159c RD |
717 | prefix = prefix[:-increment] |
718 | indent -= increment | |
719 | ||
1f780e48 | 720 | # if we have nested elements, add them here, otherwise close the element tag immediately. |
6f1a3f9c RD |
721 | xmlMemberString = filter(lambda x: len(x)>0, xmlMemberString) |
722 | if len(xmlMemberString) > 0: | |
723 | xmlString.append('>') | |
1f780e48 | 724 | if hasattr(obj, '__xmlbody__'): |
6f1a3f9c RD |
725 | xmlString.extend(xmlMemberString) |
726 | xmlString.append('</%s>%s' % (elementName, newline)) | |
1f780e48 | 727 | else: |
6f1a3f9c | 728 | xmlString.append(newline) |
bbf7159c | 729 | if (elementAdd != None): |
6f1a3f9c RD |
730 | xmlString.append('%s<%s>%s' % (prefix, elementAdd, newline)) |
731 | xmlString.extend(xmlMemberString) | |
bbf7159c | 732 | if (elementAdd != None): |
6f1a3f9c | 733 | xmlString.append('%s</%s>%s' % (prefix, elementAdd, newline)) |
bbf7159c RD |
734 | prefix = prefix[:-increment] |
735 | indent -= increment | |
6f1a3f9c | 736 | xmlString.append('%s</%s>%s' % (prefix, elementName, newline)) |
1f780e48 | 737 | else: |
6f1a3f9c | 738 | xmlString.append('/>%s' % newline) |
1f780e48 | 739 | return xmlString |