]>
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 | #---------------------------------------------------------------------------- | |
12 | from activegrid import util | |
13 | import inspect | |
14 | from types import * | |
15 | import xml.sax | |
16 | import xml.sax.handler | |
17 | import __builtin__ | |
18 | from xml.sax import saxutils | |
19 | ||
20 | ### ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed | |
21 | ||
22 | """ | |
23 | ||
24 | More documentation later, but here are some special Python attributes | |
25 | that McLane recognizes: | |
26 | ||
27 | name: __xmlname__ | |
28 | type: string | |
29 | description: the name of the xml element for the marshalled object | |
30 | ||
31 | name: __xmlattributes__ | |
32 | type: tuple or list | |
33 | description: the name(s) of the Python string attribute(s) to be | |
34 | marshalled as xml attributes instead of nested xml elements. currently | |
35 | these can only be strings since there's not a way to get the type | |
36 | information back when unmarshalling. | |
37 | ||
38 | name: __xmlexclude__ | |
39 | type: tuple or list | |
40 | description: the name(s) of the python attribute(s) to skip when | |
41 | marshalling. | |
42 | ||
43 | name: __xmlrename__ | |
44 | type: dict | |
45 | description: describes an alternate Python <-> XML name mapping. | |
46 | Normally the name mapping is the identity function. __xmlrename__ | |
47 | overrides that. The keys are the Python names, the values are their | |
48 | associated XML names. | |
49 | ||
50 | name: __xmlflattensequence__ | |
51 | type: dict, tuple, or list | |
52 | description: the name(s) of the Python sequence attribute(s) whose | |
53 | items are to be marshalled as a series of xml elements (with an | |
54 | optional keyword argument that specifies the element name to use) as | |
55 | opposed to containing them in a separate sequence element, e.g.: | |
56 | ||
57 | myseq = (1, 2) | |
58 | <!-- normal way of marshalling --> | |
59 | <myseq> | |
60 | <item objtype='int'>1</item> | |
61 | <item objtype='int'>2</item> | |
62 | </myseq> | |
63 | <!-- with __xmlflattensequence__ set to {'myseq': 'squish'} --> | |
64 | <squish objtype='int'>1</squish> | |
65 | <squish objtype='int'>2</squish> | |
66 | ||
67 | name: __xmlnamespaces__ | |
68 | type: dict | |
69 | description: a dict of the namespaces that the object uses. Each item | |
70 | in the dict should consist of a prefix,url combination where the key is | |
71 | the prefix and url is the value, e.g.: | |
72 | ||
73 | __xmlnamespaces__ = { "xsd":"http://www.w3c.org/foo.xsd" } | |
74 | ||
75 | name: __xmldefaultnamespace__ | |
76 | type: String | |
77 | description: the prefix of a namespace defined in __xmlnamespaces__ that | |
78 | should be used as the default namespace for the object. | |
79 | ||
80 | name: __xmlattrnamespaces__ | |
81 | type: dict | |
82 | description: a dict assigning the Python object's attributes to the namespaces | |
83 | defined in __xmlnamespaces__. Each item in the dict should consist of a | |
84 | prefix,attributeList combination where the key is the prefix and the value is | |
85 | a list of the Python attribute names. e.g.: | |
86 | ||
87 | __xmlattrnamespaces__ = { "ag":["firstName", "lastName", "addressLine1", "city"] } | |
88 | ||
89 | ||
90 | """ | |
91 | ||
92 | ################################################################################ | |
93 | # | |
94 | # module exceptions | |
95 | # | |
96 | ################################################################################ | |
97 | ||
98 | class Error(Exception): | |
99 | """Base class for errors in this module.""" | |
100 | pass | |
101 | ||
102 | class UnhandledTypeException(Error): | |
103 | """Exception raised when attempting to marshal an unsupported | |
104 | type. | |
105 | """ | |
106 | def __init__(self, typename): | |
107 | self.typename = typename | |
108 | def __str__(self): | |
109 | return "%s is not supported for marshalling." % str(self.typename) | |
110 | ||
111 | class XMLAttributeIsNotStringType(Error): | |
112 | """Exception raised when an object's attribute is specified to be | |
113 | marshalled as an XML attribute of the enclosing object instead of | |
114 | a nested element. | |
115 | """ | |
116 | def __init__(self, attrname, typename): | |
117 | self.attrname = attrname | |
118 | self.typename = typename | |
119 | def __str__(self): | |
120 | return """%s was set to be marshalled as an XML attribute | |
121 | instead of a nested element, but the object's type is %s, not | |
122 | string.""" % (self.attrname, self.typename) | |
123 | ||
124 | ################################################################################ | |
125 | # | |
126 | # constants and such | |
127 | # | |
128 | ################################################################################ | |
129 | ||
130 | XMLNS = 'xmlns' | |
131 | XMLNS_PREFIX = XMLNS + ':' | |
132 | XMLNS_PREFIX_LENGTH = len(XMLNS_PREFIX) | |
133 | ||
134 | BASETYPE_ELEMENT_NAME = 'item' | |
135 | MEMBERS_TO_SKIP = ('__module__', '__doc__', '__xmlname__', '__xmlattributes__', | |
136 | '__xmlexclude__', '__xmlflattensequence__', '__xmlnamespaces__', | |
137 | '__xmldefaultnamespace__', '__xmlattrnamespaces__') | |
138 | ||
139 | WELL_KNOWN_OBJECTS = { "xs:element" : "activegrid.model.schema.XsdElement", | |
140 | "xs:complexType" : "activegrid.model.schema.XsdComplexType", | |
141 | "xs:complexType" : "activegrid.model.schema.XsdComplexType", | |
142 | "xs:element" : "activegrid.model.schema.XsdElement", | |
143 | "xs:key" : "activegrid.model.schema.XsdKey", | |
144 | "xs:field" : "activegrid.model.schema.XsdKeyField", | |
145 | "xs:keyref" : "activegrid.model.schema.XsdKeyRef", | |
146 | "xs:selector" : "activegrid.model.schema.XsdKeySelector", | |
147 | "xs:schema" : "activegrid.model.schema.Schema", | |
148 | "ag:schemaOptions":"activegrid.model.schema.SchemaOptions", | |
149 | "ag:debug" : "activegrid.model.processmodel.DebugOperation", | |
150 | } | |
151 | ||
152 | ||
153 | ################################################################################ | |
154 | # | |
155 | # classes and functions | |
156 | # | |
157 | ################################################################################ | |
158 | ||
159 | def _objectfactory(objname, objargs=None, xsname=None): | |
160 | try: | |
161 | '''dynamically create an object based on the objname and return | |
162 | it. look it up in the BASETYPE_ELEMENT_MAP first. | |
163 | ''' | |
164 | ## print "_objectfactory creating an object of type %s and value %s, xsname=%s" % (objname, objargs, xsname) | |
165 | # split the objname into the typename and module path, | |
166 | # importing the module if need be. | |
167 | if not isinstance(objargs, list): | |
168 | objargs = [objargs] | |
169 | ||
170 | if (xsname): | |
171 | try: | |
172 | objname = WELL_KNOWN_OBJECTS[xsname] | |
173 | except KeyError: | |
174 | pass | |
175 | ||
176 | objtype = objname.split('.')[-1] | |
177 | pathlist = objname.split('.') | |
178 | modulename = '.'.join(pathlist[0:-1]) | |
179 | ||
180 | ## print "[objectfactory] objtype is %s" % objtype | |
181 | ## print "[objectfactory] objargs is %s" % `objargs` | |
182 | ||
183 | ## since the bool constructor will turn a string of non-zero | |
184 | ## length into True, we call it with no argument (yielding a | |
185 | ## False) if the string contains 'false' | |
186 | if objtype == 'bool' and objargs[0].lower() == 'false': | |
187 | objargs = None | |
188 | ||
189 | ## if objtype == 'str': | |
190 | ## print type(objargs) | |
191 | ## print "string we're unescaping: '%s'" % objargs[0] | |
192 | ## objargs = saxutils.unescape(objargs[0]) | |
193 | if objtype in ('float', 'int', 'str', 'long'): | |
194 | objargs = [x.strip() for x in objargs] | |
195 | ||
196 | if objtype == 'str': | |
197 | objargs = [saxutils.unescape(x) for x in objargs] | |
198 | ||
199 | if __builtin__.__dict__.has_key(objname): | |
200 | module = __builtin__ | |
201 | else: | |
202 | if modulename: | |
203 | module = __import__(modulename) | |
204 | for name in pathlist[1:-1]: | |
205 | module = module.__dict__[name] | |
206 | if objargs: | |
207 | return module.__dict__[objtype](*objargs) | |
208 | else: | |
209 | if objtype == 'None': | |
210 | return None | |
211 | return module.__dict__[objtype]() | |
212 | except KeyError: | |
213 | raise KeyError("Could not find class %s" % objname) | |
214 | ||
215 | class Element: | |
216 | def __init__(self, name, attrs=None): | |
217 | self.name = name | |
218 | self.attrs = attrs | |
219 | self.content = '' | |
220 | self.children = [] | |
221 | def getobjtype(self): | |
222 | if self.attrs.has_key('objtype'): | |
223 | return self.attrs.getValue('objtype') | |
224 | else: | |
225 | return 'str' | |
226 | ||
227 | ||
228 | class XMLObjectFactory(xml.sax.ContentHandler): | |
229 | def __init__(self): | |
230 | self.rootelement = None | |
231 | self.elementstack = [] | |
232 | xml.sax.handler.ContentHandler.__init__(self) | |
233 | ||
234 | ## ContentHandler methods | |
235 | def startElement(self, name, attrs): | |
236 | if name.find(':') > -1: # Strip namespace prefixes for now until actually looking them up in xsd | |
237 | name = name[name.index(':') + 1:] | |
238 | ## for attrname in attrs.getNames(): | |
239 | ## print "%s: %s" % (attrname, attrs.getValue(attrname)) | |
240 | element = Element(name, attrs.copy()) | |
241 | self.elementstack.append(element) | |
242 | ## print self.elementstack | |
243 | ||
244 | def characters(self, content): | |
245 | ## print "got content: %s" % content | |
246 | if content: | |
247 | self.elementstack[-1].content += content | |
248 | ||
249 | def endElement(self, name): | |
250 | ## print "[endElement] name of element we're at the end of: %s" % name | |
251 | xsname = name | |
252 | if name.find(':') > -1: # Strip namespace prefixes for now until actually looking them up in xsd | |
253 | name = name[name.index(':') + 1:] | |
254 | element = self.elementstack.pop() | |
255 | objtype = element.getobjtype() | |
256 | constructorarglist = [] | |
257 | if element.content: | |
258 | strippedElementContent = element.content.strip() | |
259 | if strippedElementContent: | |
260 | constructorarglist.append(element.content) | |
261 | obj = _objectfactory(objtype, constructorarglist, xsname) | |
262 | complexType = None | |
263 | if hasattr(obj, '__xsdcomplextype__'): | |
264 | complexType = getattr(obj, '__xsdcomplextype__') | |
265 | if len(self.elementstack) > 0: | |
266 | self.elementstack[-1].children.append((name, obj)) | |
267 | else: | |
268 | self.rootelement = obj | |
269 | if element.attrs and not isinstance(obj, list): | |
270 | for attrname, attr in element.attrs.items(): | |
271 | if attrname == XMLNS or attrname.startswith(XMLNS_PREFIX): | |
272 | if attrname.startswith(XMLNS_PREFIX): | |
273 | ns = attrname[XMLNS_PREFIX_LENGTH:] | |
274 | else: | |
275 | ns = "" | |
276 | if not hasattr(obj, '__xmlnamespaces__'): | |
277 | obj.__xmlnamespaces__ = {ns:attr} | |
278 | elif ns not in obj.__xmlnamespaces__: | |
279 | if (hasattr(obj.__class__, '__xmlnamespaces__') | |
280 | and obj.__xmlnamespaces__ is obj.__class__.__xmlnamespaces__): | |
281 | obj.__xmlnamespaces__ = dict(obj.__xmlnamespaces__) | |
282 | obj.__xmlnamespaces__[ns] = attr | |
283 | elif not attrname == 'objtype': | |
284 | if attrname.find(':') > -1: # Strip namespace prefixes for now until actually looking them up in xsd | |
285 | attrname = attrname[attrname.index(':') + 1:] | |
286 | if complexType: | |
287 | xsdElement = complexType.findElement(attrname) | |
288 | if xsdElement: | |
289 | type = xsdElement.type | |
290 | if type: | |
291 | type = xsdToPythonType(type) | |
292 | ### ToDO remove maxOccurs hack after bug 177 is fixed | |
293 | if attrname == "maxOccurs" and attr == "unbounded": | |
294 | attr = "-1" | |
295 | attr = _objectfactory(type, attr) | |
296 | util.setattrignorecase(obj, _toAttrName(obj, attrname), attr) | |
297 | ## obj.__dict__[_toAttrName(obj, attrname)] = attr | |
298 | # stuff any child attributes meant to be in a sequence via the __xmlflattensequence__ | |
299 | flattenDict = {} | |
300 | if hasattr(obj, '__xmlflattensequence__'): | |
301 | for sequencename, xmlnametuple in obj.__xmlflattensequence__.items(): | |
302 | for xmlname in xmlnametuple: | |
303 | flattenDict[xmlname] = sequencename | |
304 | ||
305 | # reattach an object's attributes to it | |
306 | for childname, child in element.children: | |
307 | if flattenDict.has_key(childname): | |
308 | sequencename = _toAttrName(obj, flattenDict[childname]) | |
309 | try: | |
310 | sequencevalue = obj.__dict__[sequencename] | |
311 | except AttributeError: | |
312 | sequencevalue = None | |
313 | if sequencevalue == None: | |
314 | sequencevalue = [] | |
315 | obj.__dict__[sequencename] = sequencevalue | |
316 | sequencevalue.append(child) | |
317 | elif isinstance(obj, list): | |
318 | obj.append(child) | |
319 | else: | |
320 | ## print "childname = %s, obj = %s, child = %s" % (childname, repr(obj), repr(child)) | |
321 | util.setattrignorecase(obj, _toAttrName(obj, childname), child) | |
322 | ## obj.__dict__[_toAttrName(obj, childname)] = child | |
323 | ||
324 | if complexType: | |
325 | for element in complexType.elements: | |
326 | if element.default: | |
327 | elementName = _toAttrName(obj, element.name) | |
328 | if ((elementName not in obj.__dict__) or (obj.__dict__[elementName] == None)): | |
329 | pythonType = xsdToPythonType(element.type) | |
330 | defaultValue = _objectfactory(pythonType, element.default) | |
331 | obj.__dict__[elementName] = defaultValue | |
332 | ||
333 | def getRootObject(self): | |
334 | return self.rootelement | |
335 | ||
336 | def _toAttrName(obj, name): | |
337 | if (hasattr(obj, "__xmlrename__")): | |
338 | for key, val in obj.__xmlrename__.iteritems(): | |
339 | if (name == val): | |
340 | name = key | |
341 | break | |
342 | ## if (name.startswith("__") and not name.endswith("__")): | |
343 | ## name = "_%s%s" % (obj.__class__.__name__, name) | |
344 | return name | |
345 | ||
346 | __typeMappingXsdToPython = { | |
347 | "string": "str", | |
348 | "char": "str", | |
349 | "varchar": "str", | |
350 | "date": "str", # ToDO Need to work out how to create python date types | |
351 | "boolean": "bool", | |
352 | "decimal": "float", # ToDO Does python have a better fixed point type? | |
353 | "int": "int", | |
354 | "long": "long", | |
355 | "float": "float", | |
356 | "bool": "bool", | |
357 | "str": "str", | |
358 | "unicode": "unicode", | |
359 | } | |
360 | ||
361 | def xsdToPythonType(xsdType): | |
362 | try: | |
363 | return __typeMappingXsdToPython[xsdType] | |
364 | except KeyError: | |
365 | raise Exception("Unknown xsd type %s" % xsdType) | |
366 | ||
367 | def _getXmlValue(pythonValue): | |
368 | if (isinstance(pythonValue, bool)): | |
369 | return str(pythonValue).lower() | |
370 | else: | |
371 | return str(pythonValue) | |
372 | ||
373 | def unmarshal(xmlstr): | |
374 | objectfactory = XMLObjectFactory() | |
375 | xml.sax.parseString(xmlstr, objectfactory) | |
376 | return objectfactory.getRootObject() | |
377 | ||
378 | ||
379 | def marshal(obj, elementName=None, nameSpacePrefix='', nameSpaces=None, prettyPrint=False, indent=0): | |
380 | if prettyPrint or indent: | |
381 | prefix = ' '*indent | |
382 | newline = '\n' | |
383 | increment = 4 | |
384 | else: | |
385 | prefix = '' | |
386 | newline = '' | |
387 | increment = 0 | |
388 | ||
389 | ## Determine the XML element name. If it isn't specified in the | |
390 | ## parameter list, look for it in the __xmlname__ Python | |
391 | ## attribute, else use the default generic BASETYPE_ELEMENT_NAME. | |
392 | if not nameSpaces: nameSpaces = {} # Need to do this since if the {} is a default parameter it gets shared by all calls into the function | |
393 | nameSpaceAttrs = '' | |
394 | if hasattr(obj, '__xmlnamespaces__'): | |
395 | for nameSpaceKey, nameSpaceUrl in getattr(obj, '__xmlnamespaces__').items(): | |
396 | if nameSpaceUrl in nameSpaces: | |
397 | nameSpaceKey = nameSpaces[nameSpaceUrl] | |
398 | else: | |
399 | ## # TODO: Wait to do this until there is shared state for use when going through the object graph | |
400 | ## origNameSpaceKey = nameSpaceKey # Make sure there is no key collision, ie: same key referencing two different URL's | |
401 | ## i = 1 | |
402 | ## while nameSpaceKey in nameSpaces.values(): | |
403 | ## nameSpaceKey = origNameSpaceKey + str(i) | |
404 | ## i += 1 | |
405 | nameSpaces[nameSpaceUrl] = nameSpaceKey | |
406 | if nameSpaceKey == '': | |
407 | nameSpaceAttrs += ' xmlns="%s" ' % (nameSpaceUrl) | |
408 | else: | |
409 | nameSpaceAttrs += ' xmlns:%s="%s" ' % (nameSpaceKey, nameSpaceUrl) | |
410 | nameSpaceAttrs = nameSpaceAttrs.rstrip() | |
411 | if hasattr(obj, '__xmldefaultnamespace__'): | |
412 | nameSpacePrefix = getattr(obj, '__xmldefaultnamespace__') + ':' | |
413 | if not elementName: | |
414 | if hasattr(obj, '__xmlname__'): | |
415 | elementName = nameSpacePrefix + obj.__xmlname__ | |
416 | else: | |
417 | elementName = nameSpacePrefix + BASETYPE_ELEMENT_NAME | |
418 | else: | |
419 | elementName = nameSpacePrefix + elementName | |
420 | ||
421 | members_to_skip = [] | |
422 | ## Add more members_to_skip based on ones the user has selected | |
423 | ## via the __xmlexclude__ attribute. | |
424 | if hasattr(obj, '__xmlexclude__'): | |
425 | members_to_skip += list(obj.__xmlexclude__) | |
426 | # Marshal the attributes that are selected to be XML attributes. | |
427 | objattrs = '' | |
428 | className = obj.__class__.__name__ | |
429 | classNamePrefix = "_" + className | |
430 | if hasattr(obj, '__xmlattributes__'): | |
431 | xmlattributes = obj.__xmlattributes__ | |
432 | members_to_skip += xmlattributes | |
433 | for attr in xmlattributes: | |
434 | internalAttrName = attr | |
435 | if (attr.startswith("__") and not attr.endswith("__")): | |
436 | internalAttrName = classNamePrefix + attr | |
437 | # Fail silently if a python attribute is specified to be | |
438 | # an XML attribute but is missing. | |
439 | try: | |
440 | value = obj.__dict__[internalAttrName] | |
441 | except KeyError: | |
442 | continue | |
443 | ## # But, check and see if it is a property first: | |
444 | ## if (hasPropertyValue(obj, attr)): | |
445 | ## value = getattr(obj, attr) | |
446 | ## else: | |
447 | ## continue | |
448 | xsdElement = None | |
449 | if hasattr(obj, '__xsdcomplextype__'): | |
450 | complexType = getattr(obj, '__xsdcomplextype__') | |
451 | xsdElement = complexType.findElement(attr) | |
452 | if xsdElement: | |
453 | default = xsdElement.default | |
454 | if default == value or default == _getXmlValue(value): | |
455 | continue | |
456 | elif value == None: | |
457 | continue | |
458 | ||
459 | # ToDO remove maxOccurs hack after bug 177 is fixed | |
460 | if attr == "maxOccurs" and value == -1: | |
461 | value = "unbounded" | |
462 | ||
463 | if isinstance(value, bool): | |
464 | if value == True: | |
465 | value = "true" | |
466 | else: | |
467 | value = "false" | |
468 | ||
469 | attrNameSpacePrefix = '' | |
470 | if hasattr(obj, '__xmlattrnamespaces__'): | |
471 | for nameSpaceKey, nameSpaceAttributes in getattr(obj, '__xmlattrnamespaces__').items(): | |
472 | if nameSpaceKey == nameSpacePrefix[:-1]: # Don't need to specify attribute namespace if it is the same as it selement | |
473 | continue | |
474 | if attr in nameSpaceAttributes: | |
475 | attrNameSpacePrefix = nameSpaceKey + ':' | |
476 | break | |
477 | ## if attr.startswith('_'): | |
478 | ## attr = attr[1:] | |
479 | if (hasattr(obj, "__xmlrename__") and attr in obj.__xmlrename__): | |
480 | attr = obj.__xmlrename__[attr] | |
481 | ||
482 | objattrs += ' %s%s="%s"' % (attrNameSpacePrefix, attr, value) | |
483 | ||
484 | objtype = type(obj) | |
485 | if isinstance(obj, NoneType): | |
486 | return '' | |
487 | # return '%s<%s objtype="None"/>%s' % (prefix, elementName, newline) | |
488 | elif isinstance(obj, bool): | |
489 | return '%s<%s objtype="bool">%s</%s>%s' % (prefix, elementName, obj, elementName, newline) | |
490 | elif isinstance(obj, int): | |
491 | return '''%s<%s objtype="int">%s</%s>%s''' % (prefix, elementName, str(obj), elementName, newline) | |
492 | elif isinstance(obj, long): | |
493 | return '%s<%s objtype="long">%s</%s>%s' % (prefix, elementName, str(obj), elementName, newline) | |
494 | elif isinstance(obj, float): | |
495 | return '%s<%s objtype="float">%s</%s>%s' % (prefix, elementName, str(obj), elementName, newline) | |
496 | elif isinstance(obj, basestring): | |
497 | return '''%s<%s>%s</%s>%s''' % (prefix, elementName, saxutils.escape(obj), elementName, newline) | |
498 | ## elif isinstance(obj, unicode): | |
499 | ## return '''%s<%s>%s</%s>%s''' % (prefix, elementName, obj, elementName, newline) | |
500 | elif isinstance(obj, list): | |
501 | if len(obj) < 1: | |
502 | return '' | |
503 | xmlString = '%s<%s objtype="list">%s' % (prefix, elementName, newline) | |
504 | for item in obj: | |
505 | xmlString += marshal(item, nameSpaces=nameSpaces, indent=indent+increment) | |
506 | xmlString += '%s</%s>%s' % (prefix, elementName, newline) | |
507 | return xmlString | |
508 | elif isinstance(obj, tuple): | |
509 | if len(obj) < 1: | |
510 | return '' | |
511 | xmlString = '%s<%s objtype="list" mutable="false">%s' % (prefix, elementName, newline) | |
512 | for item in obj: | |
513 | xmlString += marshal(item, nameSpaces=nameSpaces, indent=indent+increment) | |
514 | xmlString += '%s</%s>%s' % (prefix, elementName, newline) | |
515 | return xmlString | |
516 | elif isinstance(obj, dict): | |
517 | xmlString = '%s<%s objtype="dict">%s' % (prefix, elementName, newline) | |
518 | subprefix = prefix + ' '*increment | |
519 | subindent = indent + 2*increment | |
520 | for key, val in obj.iteritems(): | |
521 | xmlString += "%s<key>%s%s%s</key>%s%s<value>%s%s%s</value>%s" \ | |
522 | % (subprefix, newline, marshal(key, indent=subindent), subprefix, newline, subprefix, newline, marshal(val, nameSpaces=nameSpaces, indent=subindent), subprefix, newline) | |
523 | xmlString += '%s</%s>%s' % (prefix, elementName, newline) | |
524 | return xmlString | |
525 | else: | |
526 | moduleName = obj.__class__.__module__ | |
527 | if (moduleName == "activegrid.model.schema"): | |
528 | xmlString = '%s<%s%s%s' % (prefix, elementName, nameSpaceAttrs, objattrs) | |
529 | else: | |
530 | xmlString = '%s<%s%s%s objtype="%s.%s"' % (prefix, elementName, nameSpaceAttrs, objattrs, moduleName, className) | |
531 | # get the member, value pairs for the object, filtering out | |
532 | # the types we don't support. | |
533 | xmlMemberString = '' | |
534 | if hasattr(obj, '__xmlbody__'): | |
535 | xmlMemberString = getattr(obj, obj.__xmlbody__) | |
536 | else: | |
537 | entryList = obj.__dict__.items() | |
538 | ## # Add in properties | |
539 | ## for key in obj.__class__.__dict__.iterkeys(): | |
540 | ## if (key not in members_to_skip and key not in obj.__dict__ | |
541 | ## and hasPropertyValue(obj, key)): | |
542 | ## value = getattr(obj, key) | |
543 | ## entryList.append((key, value)) | |
544 | entryList.sort() | |
545 | for name, value in entryList: | |
546 | ## # special name handling for private "__*" attributes: | |
547 | ## # remove the _<class-name> added by Python | |
548 | ## if name.startswith(classNamePrefix): name = name[len(classNamePrefix):] | |
549 | if name in members_to_skip: continue | |
550 | if name.startswith('__') and name.endswith('__'): continue | |
551 | ## idx = name.find('__') | |
552 | ## if idx > 0: | |
553 | ## newName = name[idx+2:] | |
554 | ## if newName: | |
555 | ## name = newName | |
556 | subElementNameSpacePrefix = nameSpacePrefix | |
557 | if hasattr(obj, '__xmlattrnamespaces__'): | |
558 | for nameSpaceKey, nameSpaceValues in getattr(obj, '__xmlattrnamespaces__').items(): | |
559 | if name in nameSpaceValues: | |
560 | subElementNameSpacePrefix = nameSpaceKey + ':' | |
561 | break | |
562 | # handle sequences listed in __xmlflattensequence__ | |
563 | # specially: instead of listing the contained items inside | |
564 | # of a separate list, as god intended, list them inside | |
565 | # the object containing the sequence. | |
566 | if hasattr(obj, '__xmlflattensequence__') and name in obj.__xmlflattensequence__ and value: | |
567 | try: | |
568 | xmlnametuple = obj.__xmlflattensequence__[name] | |
569 | xmlname = None | |
570 | if len(xmlnametuple) == 1: | |
571 | xmlname = xmlnametuple[0] | |
572 | except: | |
573 | xmlname = name | |
574 | ## xmlname = name.lower() | |
575 | for seqitem in value: | |
576 | xmlMemberString += marshal(seqitem, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment) | |
577 | else: | |
578 | if (hasattr(obj, "__xmlrename__") and name in obj.__xmlrename__): | |
579 | xmlname = obj.__xmlrename__[name] | |
580 | else: | |
581 | xmlname = name | |
582 | ## xmlname = name.lower() | |
583 | ## # skip | |
584 | ## if xmlname.startswith('_') and not xmlname.startswith('__'): | |
585 | ## xmlname = xmlname[1:] | |
586 | ## if (indent > 30): | |
587 | ## print "getting pretty deep, xmlname = ", xmlname | |
588 | xmlMemberString += marshal(value, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment) | |
589 | # if we have nested elements, add them here, otherwise close the element tag immediately. | |
590 | if xmlMemberString: | |
591 | xmlString += '>' | |
592 | if hasattr(obj, '__xmlbody__'): | |
593 | xmlString += xmlMemberString | |
594 | xmlString += '</%s>%s' % (elementName, newline) | |
595 | else: | |
596 | xmlString += newline | |
597 | xmlString += xmlMemberString | |
598 | xmlString += '%s</%s>%s' % (prefix, elementName, newline) | |
599 | else: | |
600 | xmlString = xmlString + '/>%s' % newline | |
601 | return xmlString | |
602 | ||
603 | # We don't use this anymore but in case we want to get properties this is how | |
604 | # you do it | |
605 | def hasPropertyValue(obj, attr): | |
606 | hasProp = False | |
607 | try: | |
608 | prop = obj.__class__.__dict__[attr] | |
609 | if (isinstance(prop, property)): | |
610 | hasProp = hasattr(obj, attr) | |
611 | if (hasProp): | |
612 | # It's a property and it has a value but sometimes we don't want it. | |
613 | # If there is a _hasattr method execute it and the | |
614 | # result will tell us whether to include this value | |
615 | try: | |
616 | hasProp = obj._hasattr(attr) | |
617 | except: | |
618 | pass | |
619 | except KeyError: | |
620 | pass | |
621 | return hasProp | |
622 | ||
623 | if __name__ == '__main__': | |
624 | from xmlmarshallertests import Person, marshalledint, marshalledlist | |
625 | ||
626 | l = [1, 2, 3] | |
627 | d = {'1': 1, '2': 2} | |
628 | outerlist = [l] | |
629 | xmlstr = marshal(d, "d", prettyPrint=True) | |
630 | print xmlstr | |
631 | ||
632 | person = Person() | |
633 | person.firstName = "Albert" | |
634 | person.lastName = "Camus" | |
635 | person.addressLine1 = "23 Absurd St." | |
636 | person.city = "Ennui" | |
637 | person.state = "MO" | |
638 | person.zip = "54321" | |
639 | person._phoneNumber = "808-303-2323" | |
640 | person.favoriteWords = ['angst', 'ennui', 'existence'] | |
641 | person.weight = 150 | |
642 | ||
643 | xmlstring = marshal(person, 'person', prettyPrint=True) | |
644 | print xmlstring | |
645 | ||
646 | obj = unmarshal(marshalledlist) | |
647 | print "obj has type %s and value %s" % (type(obj), str(obj)) | |
648 | for item in obj: | |
649 | print "item: %s" % str(item) |