]>
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 | ||
bbf7159c RD |
89 | name: __xmlattrgroups__ |
90 | type: dict | |
91 | description: a dict specifying groups of attributes to be wrapped in an enclosing tag. | |
92 | The key is the name of the enclosing tag; the value is a list of attributes to include | |
93 | within it. e.g. | |
94 | ||
95 | __xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]} | |
1f780e48 RD |
96 | |
97 | """ | |
98 | ||
99 | ################################################################################ | |
100 | # | |
101 | # module exceptions | |
102 | # | |
103 | ################################################################################ | |
104 | ||
105 | class Error(Exception): | |
106 | """Base class for errors in this module.""" | |
107 | pass | |
108 | ||
109 | class UnhandledTypeException(Error): | |
110 | """Exception raised when attempting to marshal an unsupported | |
111 | type. | |
112 | """ | |
113 | def __init__(self, typename): | |
114 | self.typename = typename | |
115 | def __str__(self): | |
116 | return "%s is not supported for marshalling." % str(self.typename) | |
117 | ||
118 | class XMLAttributeIsNotStringType(Error): | |
119 | """Exception raised when an object's attribute is specified to be | |
120 | marshalled as an XML attribute of the enclosing object instead of | |
121 | a nested element. | |
122 | """ | |
123 | def __init__(self, attrname, typename): | |
124 | self.attrname = attrname | |
125 | self.typename = typename | |
126 | def __str__(self): | |
127 | return """%s was set to be marshalled as an XML attribute | |
128 | instead of a nested element, but the object's type is %s, not | |
129 | string.""" % (self.attrname, self.typename) | |
130 | ||
131 | ################################################################################ | |
132 | # | |
133 | # constants and such | |
134 | # | |
135 | ################################################################################ | |
136 | ||
137 | XMLNS = 'xmlns' | |
138 | XMLNS_PREFIX = XMLNS + ':' | |
139 | XMLNS_PREFIX_LENGTH = len(XMLNS_PREFIX) | |
140 | ||
141 | BASETYPE_ELEMENT_NAME = 'item' | |
bbf7159c RD |
142 | |
143 | # This list doesn't seem to be used. | |
144 | # Internal documentation or useless? You make the call! | |
1f780e48 RD |
145 | MEMBERS_TO_SKIP = ('__module__', '__doc__', '__xmlname__', '__xmlattributes__', |
146 | '__xmlexclude__', '__xmlflattensequence__', '__xmlnamespaces__', | |
bbf7159c RD |
147 | '__xmldefaultnamespace__', '__xmlattrnamespaces__', |
148 | '__xmlattrgroups__') | |
149 | ||
150 | #WELL_KNOWN_OBJECTS = { #"xs:element" : "activegrid.model.schema.XsdElement", | |
151 | #"xs:complexType" : "activegrid.model.schema.XsdComplexType", | |
152 | #"xs:sequence" : "activegrid.model.schema.XsdSequence", | |
153 | #"xs:element" : "activegrid.model.schema.XsdElement", | |
154 | #"xs:key" : "activegrid.model.schema.XsdKey", | |
155 | #"xs:field" : "activegrid.model.schema.XsdKeyField", | |
156 | #"xs:keyref" : "activegrid.model.schema.XsdKeyRef", | |
157 | #"xs:selector" : "activegrid.model.schema.XsdKeySelector", | |
158 | #"xs:schema" : "activegrid.model.schema.Schema", | |
159 | #"ag:schemaOptions":"activegrid.model.schema.SchemaOptions", | |
160 | #"ag:debug" : "activegrid.model.processmodel.DebugOperation", | |
161 | #"ag:body" : "activegrid.model.processmodel.Body", # alan (start) | |
162 | #"ag:cssRule" : "activegrid.model.processmodel.CssRule", | |
163 | #"ag:datasource" : "activegrid.data.dataservice.DataSource", | |
164 | #"ag:deployment" : "activegrid.server.deployment.Deployment", | |
165 | #"ag:glue" : "activegrid.model.processmodel.Glue", | |
166 | #"ag:hr" : "activegrid.model.processmodel.HorizontalRow", | |
167 | #"ag:image" : "activegrid.model.processmodel.Image", | |
168 | #"ag:inputs" : "activegrid.model.processmodel.Inputs", | |
169 | #"ag:label" : "activegrid.model.processmodel.Label", | |
170 | #"ag:processmodel" : "activegrid.model.processmodel.ProcessModel", | |
171 | #"ag:processmodelref" : "activegrid.server.deployment.ProcessModelRef", | |
172 | #"ag:query" : "activegrid.model.processmodel.Query", | |
173 | #"ag:schemaref" : "activegrid.server.deployment.SchemaRef", | |
174 | #"ag:set" : "activegrid.model.processmodel.SetOperation", | |
175 | #"ag:text" : "activegrid.model.processmodel.Text", | |
176 | #"ag:title" : "activegrid.model.processmodel.Title", | |
177 | #"ag:view" : "activegrid.model.processmodel.View", | |
178 | #"bpws:case" : "activegrid.model.processmodel.BPELCase", | |
179 | #"bpws:invoke" : "activegrid.model.processmodel.BPELInvoke", | |
180 | #"bpws:otherwise" : "activegrid.model.processmodel.BPELOtherwise", | |
181 | #"bpws:process" : "activegrid.model.processmodel.BPELProcess", | |
182 | #"bpws:reply" : "activegrid.model.processmodel.BPELReply", | |
183 | #"bpws:switch" : "activegrid.model.processmodel.BPELSwitch", | |
184 | #"bpws:variable" : "activegrid.model.processmodel.BPELVariable", | |
185 | #"projectmodel" : "activegrid.tool.ProjectEditor.ProjectModel", | |
186 | #"wsdl:message" : "activegrid.model.processmodel.WSDLMessage", | |
187 | #"wsdl:part" : "activegrid.model.processmodel.WSDLPart", | |
188 | #"xforms:group" : "activegrid.model.processmodel.XFormsGroup", | |
189 | #"xforms:input" : "activegrid.model.processmodel.XFormsInput", | |
190 | #"xforms:label" : "activegrid.model.processmodel.XFormsLabel", | |
191 | #"xforms:output" : "activegrid.model.processmodel.XFormsOutput", | |
192 | #"xforms:secret" : "activegrid.model.processmodel.XFormsSecret", | |
193 | #"xforms:submit" : "activegrid.model.processmodel.XFormsSubmit"} # alan(end) | |
1f780e48 RD |
194 | |
195 | ||
196 | ################################################################################ | |
197 | # | |
198 | # classes and functions | |
199 | # | |
200 | ################################################################################ | |
201 | ||
202 | def _objectfactory(objname, objargs=None, xsname=None): | |
203 | try: | |
204 | '''dynamically create an object based on the objname and return | |
205 | it. look it up in the BASETYPE_ELEMENT_MAP first. | |
206 | ''' | |
1f780e48 RD |
207 | # split the objname into the typename and module path, |
208 | # importing the module if need be. | |
209 | if not isinstance(objargs, list): | |
210 | objargs = [objargs] | |
211 | ||
212 | if (xsname): | |
213 | try: | |
bbf7159c | 214 | objname = knownGlobalTypes[xsname] |
1f780e48 RD |
215 | except KeyError: |
216 | pass | |
bbf7159c RD |
217 | |
218 | ## print "[objectfactory] creating an object of type %s and value %s, xsname=%s" % (objname, objargs, xsname) | |
1f780e48 RD |
219 | objtype = objname.split('.')[-1] |
220 | pathlist = objname.split('.') | |
221 | modulename = '.'.join(pathlist[0:-1]) | |
222 | ||
223 | ## print "[objectfactory] objtype is %s" % objtype | |
224 | ## print "[objectfactory] objargs is %s" % `objargs` | |
225 | ||
226 | ## since the bool constructor will turn a string of non-zero | |
227 | ## length into True, we call it with no argument (yielding a | |
228 | ## False) if the string contains 'false' | |
229 | if objtype == 'bool' and objargs[0].lower() == 'false': | |
230 | objargs = None | |
231 | ||
232 | ## if objtype == 'str': | |
233 | ## print type(objargs) | |
234 | ## print "string we're unescaping: '%s'" % objargs[0] | |
235 | ## objargs = saxutils.unescape(objargs[0]) | |
236 | if objtype in ('float', 'int', 'str', 'long'): | |
237 | objargs = [x.strip() for x in objargs] | |
238 | ||
239 | if objtype == 'str': | |
240 | objargs = [saxutils.unescape(x) for x in objargs] | |
241 | ||
242 | if __builtin__.__dict__.has_key(objname): | |
243 | module = __builtin__ | |
244 | else: | |
245 | if modulename: | |
246 | module = __import__(modulename) | |
247 | for name in pathlist[1:-1]: | |
248 | module = module.__dict__[name] | |
249 | if objargs: | |
250 | return module.__dict__[objtype](*objargs) | |
251 | else: | |
252 | if objtype == 'None': | |
253 | return None | |
254 | return module.__dict__[objtype]() | |
255 | except KeyError: | |
256 | raise KeyError("Could not find class %s" % objname) | |
257 | ||
258 | class Element: | |
259 | def __init__(self, name, attrs=None): | |
260 | self.name = name | |
261 | self.attrs = attrs | |
262 | self.content = '' | |
263 | self.children = [] | |
264 | def getobjtype(self): | |
265 | if self.attrs.has_key('objtype'): | |
266 | return self.attrs.getValue('objtype') | |
267 | else: | |
268 | return 'str' | |
bbf7159c RD |
269 | def toString(self): |
270 | print " name = ", self.name, "; attrs = ", self.attrs, "number of children = ", len(self.children) | |
271 | i = -1 | |
272 | for child in self.children: | |
273 | i = i + 1 | |
274 | childClass = child.__class__.__name__ | |
275 | print " Child ", i, " class: ",childClass | |
1f780e48 RD |
276 | |
277 | ||
278 | class XMLObjectFactory(xml.sax.ContentHandler): | |
279 | def __init__(self): | |
280 | self.rootelement = None | |
281 | self.elementstack = [] | |
282 | xml.sax.handler.ContentHandler.__init__(self) | |
283 | ||
bbf7159c RD |
284 | def toString(self): |
285 | print "-----XMLObjectFactory Dump-------------------------------" | |
286 | if (self.rootelement == None): | |
287 | print "rootelement is None" | |
288 | else: | |
289 | print "rootelement is an object" | |
290 | i = -1 | |
291 | print "length of elementstack is: ", len(self.elementstack) | |
292 | for e in self.elementstack: | |
293 | i = i + 1 | |
294 | print "elementstack[", i, "]: " | |
295 | e.toString() | |
296 | print "-----end XMLObjectFactory--------------------------------" | |
297 | ||
1f780e48 RD |
298 | ## ContentHandler methods |
299 | def startElement(self, name, attrs): | |
bbf7159c | 300 | ## print "startElement for name: ", name |
1f780e48 RD |
301 | if name.find(':') > -1: # Strip namespace prefixes for now until actually looking them up in xsd |
302 | name = name[name.index(':') + 1:] | |
303 | ## for attrname in attrs.getNames(): | |
304 | ## print "%s: %s" % (attrname, attrs.getValue(attrname)) | |
305 | element = Element(name, attrs.copy()) | |
306 | self.elementstack.append(element) | |
307 | ## print self.elementstack | |
308 | ||
309 | def characters(self, content): | |
310 | ## print "got content: %s" % content | |
311 | if content: | |
312 | self.elementstack[-1].content += content | |
313 | ||
314 | def endElement(self, name): | |
315 | ## print "[endElement] name of element we're at the end of: %s" % name | |
316 | xsname = name | |
317 | if name.find(':') > -1: # Strip namespace prefixes for now until actually looking them up in xsd | |
318 | name = name[name.index(':') + 1:] | |
bbf7159c | 319 | oldChildren = self.elementstack[-1].children |
1f780e48 | 320 | element = self.elementstack.pop() |
bbf7159c RD |
321 | if ((len(self.elementstack) > 1) and (self.elementstack[-1].getobjtype() == "None")): |
322 | parentElement = self.elementstack[-2] | |
323 | ## print "[endElement] %s: found parent with objtype==None: using its grandparent" % name | |
324 | elif (len(self.elementstack) > 0): | |
325 | parentElement = self.elementstack[-1] | |
1f780e48 | 326 | objtype = element.getobjtype() |
bbf7159c RD |
327 | ## print "element objtype is: ", objtype |
328 | if (objtype == "None"): | |
329 | ## print "[endElement] %s: skipping a (objtype==None) end tag" % name | |
330 | return | |
1f780e48 RD |
331 | constructorarglist = [] |
332 | if element.content: | |
333 | strippedElementContent = element.content.strip() | |
334 | if strippedElementContent: | |
335 | constructorarglist.append(element.content) | |
bbf7159c | 336 | ## print "[endElement] calling objectfactory" |
1f780e48 RD |
337 | obj = _objectfactory(objtype, constructorarglist, xsname) |
338 | complexType = None | |
339 | if hasattr(obj, '__xsdcomplextype__'): | |
340 | complexType = getattr(obj, '__xsdcomplextype__') | |
bbf7159c RD |
341 | if (hasattr(obj, '__xmlname__') and getattr(obj, '__xmlname__') == "sequence"): |
342 | ## print "[endElement] sequence found" | |
343 | ## self.toString() | |
344 | self.elementstack[-1].children = oldChildren | |
345 | ## self.toString() | |
346 | ## print "done moving sequence stuff; returning" | |
347 | return | |
1f780e48 | 348 | if len(self.elementstack) > 0: |
bbf7159c RD |
349 | ## print "[endElement] appending child with name: ", name, "; objtype: ", objtype |
350 | parentElement.children.append((name, obj)) | |
351 | ## print "parentElement now has ", len(parentElement.children), " children" | |
1f780e48 RD |
352 | else: |
353 | self.rootelement = obj | |
354 | if element.attrs and not isinstance(obj, list): | |
bbf7159c | 355 | ## print "[endElement] %s: element has attrs and the obj is not a list" % name |
1f780e48 RD |
356 | for attrname, attr in element.attrs.items(): |
357 | if attrname == XMLNS or attrname.startswith(XMLNS_PREFIX): | |
358 | if attrname.startswith(XMLNS_PREFIX): | |
359 | ns = attrname[XMLNS_PREFIX_LENGTH:] | |
360 | else: | |
361 | ns = "" | |
362 | if not hasattr(obj, '__xmlnamespaces__'): | |
363 | obj.__xmlnamespaces__ = {ns:attr} | |
364 | elif ns not in obj.__xmlnamespaces__: | |
365 | if (hasattr(obj.__class__, '__xmlnamespaces__') | |
366 | and obj.__xmlnamespaces__ is obj.__class__.__xmlnamespaces__): | |
367 | obj.__xmlnamespaces__ = dict(obj.__xmlnamespaces__) | |
368 | obj.__xmlnamespaces__[ns] = attr | |
369 | elif not attrname == 'objtype': | |
370 | if attrname.find(':') > -1: # Strip namespace prefixes for now until actually looking them up in xsd | |
371 | attrname = attrname[attrname.index(':') + 1:] | |
372 | if complexType: | |
373 | xsdElement = complexType.findElement(attrname) | |
374 | if xsdElement: | |
375 | type = xsdElement.type | |
376 | if type: | |
377 | type = xsdToPythonType(type) | |
378 | ### ToDO remove maxOccurs hack after bug 177 is fixed | |
379 | if attrname == "maxOccurs" and attr == "unbounded": | |
380 | attr = "-1" | |
381 | attr = _objectfactory(type, attr) | |
382 | util.setattrignorecase(obj, _toAttrName(obj, attrname), attr) | |
383 | ## obj.__dict__[_toAttrName(obj, attrname)] = attr | |
384 | # stuff any child attributes meant to be in a sequence via the __xmlflattensequence__ | |
385 | flattenDict = {} | |
386 | if hasattr(obj, '__xmlflattensequence__'): | |
bbf7159c RD |
387 | ## print "[endElement] %s: obj has __xmlflattensequence__" % name |
388 | if (isinstance(obj.__xmlflattensequence__,dict)): | |
389 | ## print "[endElement] dict with obj.__xmlflattensequence__.items: ", obj.__xmlflattensequence__.items() | |
390 | for sequencename, xmlnametuple in obj.__xmlflattensequence__.items(): | |
391 | for xmlname in xmlnametuple: | |
392 | ## print "[endElement]: adding flattenDict[%s] = %s" % (xmlname, sequencename) | |
393 | flattenDict[xmlname] = sequencename | |
394 | # handle __xmlflattensequence__ list/tuple (i.e. no element rename) | |
395 | elif (isinstance(obj.__xmlflattensequence__,list) or isinstance(obj.__xmlflattensequence__,tuple)): | |
396 | for sequencename in obj.__xmlflattensequence__: | |
397 | flattenDict[sequencename] = sequencename | |
398 | else: | |
399 | raise "Invalid type for __xmlflattensequence___ : it must be a dict, list, or tuple" | |
1f780e48 RD |
400 | |
401 | # reattach an object's attributes to it | |
402 | for childname, child in element.children: | |
bbf7159c | 403 | ## print "[endElement] childname is: ", childname, "; child is: ", child |
1f780e48 RD |
404 | if flattenDict.has_key(childname): |
405 | sequencename = _toAttrName(obj, flattenDict[childname]) | |
bbf7159c | 406 | ## print "[endElement] sequencename is: ", sequencename |
1f780e48 | 407 | try: |
bbf7159c | 408 | ## print "[endElement] obj.__dict__ is: ", obj.__dict__ |
1f780e48 RD |
409 | sequencevalue = obj.__dict__[sequencename] |
410 | except AttributeError: | |
411 | sequencevalue = None | |
bbf7159c RD |
412 | except KeyError: |
413 | sequencevalue = None | |
1f780e48 RD |
414 | if sequencevalue == None: |
415 | sequencevalue = [] | |
416 | obj.__dict__[sequencename] = sequencevalue | |
417 | sequencevalue.append(child) | |
418 | elif isinstance(obj, list): | |
bbf7159c | 419 | ## print "appended childname = ", childname |
1f780e48 RD |
420 | obj.append(child) |
421 | else: | |
422 | ## print "childname = %s, obj = %s, child = %s" % (childname, repr(obj), repr(child)) | |
423 | util.setattrignorecase(obj, _toAttrName(obj, childname), child) | |
bbf7159c | 424 | obj.__dict__[_toAttrName(obj, childname)] = child |
1f780e48 RD |
425 | |
426 | if complexType: | |
427 | for element in complexType.elements: | |
428 | if element.default: | |
429 | elementName = _toAttrName(obj, element.name) | |
430 | if ((elementName not in obj.__dict__) or (obj.__dict__[elementName] == None)): | |
431 | pythonType = xsdToPythonType(element.type) | |
432 | defaultValue = _objectfactory(pythonType, element.default) | |
433 | obj.__dict__[elementName] = defaultValue | |
434 | ||
435 | def getRootObject(self): | |
436 | return self.rootelement | |
437 | ||
438 | def _toAttrName(obj, name): | |
439 | if (hasattr(obj, "__xmlrename__")): | |
440 | for key, val in obj.__xmlrename__.iteritems(): | |
441 | if (name == val): | |
442 | name = key | |
443 | break | |
444 | ## if (name.startswith("__") and not name.endswith("__")): | |
445 | ## name = "_%s%s" % (obj.__class__.__name__, name) | |
446 | return name | |
447 | ||
448 | __typeMappingXsdToPython = { | |
449 | "string": "str", | |
450 | "char": "str", | |
451 | "varchar": "str", | |
452 | "date": "str", # ToDO Need to work out how to create python date types | |
453 | "boolean": "bool", | |
454 | "decimal": "float", # ToDO Does python have a better fixed point type? | |
455 | "int": "int", | |
456 | "long": "long", | |
457 | "float": "float", | |
458 | "bool": "bool", | |
459 | "str": "str", | |
460 | "unicode": "unicode", | |
bbf7159c RD |
461 | "short": "int", |
462 | "duration": "str", # see above (date) | |
463 | "datetime": "str", # see above (date) | |
464 | "time": "str", # see above (date) | |
465 | "double": "float", | |
1f780e48 RD |
466 | } |
467 | ||
468 | def xsdToPythonType(xsdType): | |
469 | try: | |
470 | return __typeMappingXsdToPython[xsdType] | |
471 | except KeyError: | |
472 | raise Exception("Unknown xsd type %s" % xsdType) | |
473 | ||
474 | def _getXmlValue(pythonValue): | |
475 | if (isinstance(pythonValue, bool)): | |
476 | return str(pythonValue).lower() | |
477 | else: | |
478 | return str(pythonValue) | |
479 | ||
bbf7159c RD |
480 | def unmarshal(xmlstr, knownTypes=None): |
481 | global knownGlobalTypes | |
482 | if (knownTypes == None): | |
483 | knownGlobalTypes = {} | |
484 | else: | |
485 | knownGlobalTypes = knownTypes | |
1f780e48 RD |
486 | objectfactory = XMLObjectFactory() |
487 | xml.sax.parseString(xmlstr, objectfactory) | |
488 | return objectfactory.getRootObject() | |
489 | ||
490 | ||
bbf7159c | 491 | def marshal(obj, elementName=None, nameSpacePrefix='', nameSpaces=None, prettyPrint=False, indent=0, knownTypes=None): |
1f780e48 RD |
492 | if prettyPrint or indent: |
493 | prefix = ' '*indent | |
494 | newline = '\n' | |
495 | increment = 4 | |
496 | else: | |
497 | prefix = '' | |
498 | newline = '' | |
499 | increment = 0 | |
500 | ||
501 | ## Determine the XML element name. If it isn't specified in the | |
502 | ## parameter list, look for it in the __xmlname__ Python | |
503 | ## attribute, else use the default generic BASETYPE_ELEMENT_NAME. | |
504 | if not nameSpaces: nameSpaces = {} # Need to do this since if the {} is a default parameter it gets shared by all calls into the function | |
505 | nameSpaceAttrs = '' | |
bbf7159c RD |
506 | if knownTypes == None: |
507 | knownTypes = {} | |
1f780e48 RD |
508 | if hasattr(obj, '__xmlnamespaces__'): |
509 | for nameSpaceKey, nameSpaceUrl in getattr(obj, '__xmlnamespaces__').items(): | |
510 | if nameSpaceUrl in nameSpaces: | |
511 | nameSpaceKey = nameSpaces[nameSpaceUrl] | |
512 | else: | |
513 | ## # TODO: Wait to do this until there is shared state for use when going through the object graph | |
514 | ## origNameSpaceKey = nameSpaceKey # Make sure there is no key collision, ie: same key referencing two different URL's | |
515 | ## i = 1 | |
516 | ## while nameSpaceKey in nameSpaces.values(): | |
517 | ## nameSpaceKey = origNameSpaceKey + str(i) | |
518 | ## i += 1 | |
519 | nameSpaces[nameSpaceUrl] = nameSpaceKey | |
520 | if nameSpaceKey == '': | |
521 | nameSpaceAttrs += ' xmlns="%s" ' % (nameSpaceUrl) | |
522 | else: | |
523 | nameSpaceAttrs += ' xmlns:%s="%s" ' % (nameSpaceKey, nameSpaceUrl) | |
524 | nameSpaceAttrs = nameSpaceAttrs.rstrip() | |
525 | if hasattr(obj, '__xmldefaultnamespace__'): | |
bbf7159c | 526 | nameSpacePrefix = getattr(obj, '__xmldefaultnamespace__') + ':' |
1f780e48 RD |
527 | if not elementName: |
528 | if hasattr(obj, '__xmlname__'): | |
529 | elementName = nameSpacePrefix + obj.__xmlname__ | |
530 | else: | |
531 | elementName = nameSpacePrefix + BASETYPE_ELEMENT_NAME | |
532 | else: | |
533 | elementName = nameSpacePrefix + elementName | |
bbf7159c RD |
534 | if hasattr(obj, '__xmlsequencer__'): |
535 | elementAdd = obj.__xmlsequencer__ | |
536 | else: | |
537 | elementAdd = None | |
538 | ||
539 | ## print "marshal: entered with elementName: ", elementName | |
1f780e48 RD |
540 | members_to_skip = [] |
541 | ## Add more members_to_skip based on ones the user has selected | |
542 | ## via the __xmlexclude__ attribute. | |
543 | if hasattr(obj, '__xmlexclude__'): | |
bbf7159c | 544 | ## print "marshal: found __xmlexclude__" |
1f780e48 RD |
545 | members_to_skip += list(obj.__xmlexclude__) |
546 | # Marshal the attributes that are selected to be XML attributes. | |
547 | objattrs = '' | |
548 | className = obj.__class__.__name__ | |
549 | classNamePrefix = "_" + className | |
550 | if hasattr(obj, '__xmlattributes__'): | |
bbf7159c | 551 | ## print "marshal: found __xmlattributes__" |
1f780e48 RD |
552 | xmlattributes = obj.__xmlattributes__ |
553 | members_to_skip += xmlattributes | |
554 | for attr in xmlattributes: | |
555 | internalAttrName = attr | |
556 | if (attr.startswith("__") and not attr.endswith("__")): | |
557 | internalAttrName = classNamePrefix + attr | |
558 | # Fail silently if a python attribute is specified to be | |
559 | # an XML attribute but is missing. | |
bbf7159c | 560 | ## print "marshal: processing attribute ", internalAttrName |
1f780e48 RD |
561 | try: |
562 | value = obj.__dict__[internalAttrName] | |
563 | except KeyError: | |
564 | continue | |
565 | ## # But, check and see if it is a property first: | |
566 | ## if (hasPropertyValue(obj, attr)): | |
567 | ## value = getattr(obj, attr) | |
568 | ## else: | |
569 | ## continue | |
570 | xsdElement = None | |
571 | if hasattr(obj, '__xsdcomplextype__'): | |
bbf7159c | 572 | ## print "marshal: found __xsdcomplextype__" |
1f780e48 RD |
573 | complexType = getattr(obj, '__xsdcomplextype__') |
574 | xsdElement = complexType.findElement(attr) | |
575 | if xsdElement: | |
576 | default = xsdElement.default | |
577 | if default == value or default == _getXmlValue(value): | |
578 | continue | |
579 | elif value == None: | |
580 | continue | |
581 | ||
582 | # ToDO remove maxOccurs hack after bug 177 is fixed | |
583 | if attr == "maxOccurs" and value == -1: | |
584 | value = "unbounded" | |
585 | ||
586 | if isinstance(value, bool): | |
587 | if value == True: | |
588 | value = "true" | |
589 | else: | |
590 | value = "false" | |
591 | ||
592 | attrNameSpacePrefix = '' | |
593 | if hasattr(obj, '__xmlattrnamespaces__'): | |
bbf7159c | 594 | ## print "marshal: found __xmlattrnamespaces__" |
1f780e48 RD |
595 | for nameSpaceKey, nameSpaceAttributes in getattr(obj, '__xmlattrnamespaces__').items(): |
596 | if nameSpaceKey == nameSpacePrefix[:-1]: # Don't need to specify attribute namespace if it is the same as it selement | |
597 | continue | |
598 | if attr in nameSpaceAttributes: | |
599 | attrNameSpacePrefix = nameSpaceKey + ':' | |
600 | break | |
601 | ## if attr.startswith('_'): | |
602 | ## attr = attr[1:] | |
603 | if (hasattr(obj, "__xmlrename__") and attr in obj.__xmlrename__): | |
bbf7159c | 604 | ## print "marshal: found __xmlrename__ (and its attribute)" |
1f780e48 RD |
605 | attr = obj.__xmlrename__[attr] |
606 | ||
607 | objattrs += ' %s%s="%s"' % (attrNameSpacePrefix, attr, value) | |
bbf7159c | 608 | ## print "marshal: new objattrs is: ", objattrs |
1f780e48 RD |
609 | |
610 | objtype = type(obj) | |
611 | if isinstance(obj, NoneType): | |
bbf7159c | 612 | #print "marshal: skipping an element with no type" |
1f780e48 RD |
613 | return '' |
614 | # return '%s<%s objtype="None"/>%s' % (prefix, elementName, newline) | |
615 | elif isinstance(obj, bool): | |
bbf7159c RD |
616 | xmlString = '%s<%s objtype="bool">%s</%s>%s' % (prefix, elementName, obj, elementName, newline) |
617 | #print "marshal: returning a bool element: \n", xmlString | |
618 | return xmlString | |
1f780e48 | 619 | elif isinstance(obj, int): |
bbf7159c RD |
620 | xmlString = '''%s<%s objtype="int">%s</%s>%s''' % (prefix, elementName, str(obj), elementName, newline) |
621 | #print "marshal: returning a int element: \n", xmlString | |
622 | return xmlString | |
1f780e48 | 623 | elif isinstance(obj, long): |
bbf7159c RD |
624 | xmlString = '%s<%s objtype="long">%s</%s>%s' % (prefix, elementName, str(obj), elementName, newline) |
625 | #print "marshal: returning a long element: \n", xmlString | |
626 | return xmlString | |
1f780e48 | 627 | elif isinstance(obj, float): |
bbf7159c RD |
628 | xmlString = '%s<%s objtype="float">%s</%s>%s' % (prefix, elementName, str(obj), elementName, newline) |
629 | #print "marshal: returning a float element: \n", xmlString | |
630 | return xmlString | |
1f780e48 | 631 | elif isinstance(obj, basestring): |
bbf7159c RD |
632 | xmlString = '''%s<%s>%s</%s>%s''' % (prefix, elementName, saxutils.escape(obj), elementName, newline) |
633 | #print "marshal: returning a str element: \n", xmlString | |
634 | return xmlString | |
1f780e48 RD |
635 | ## elif isinstance(obj, unicode): |
636 | ## return '''%s<%s>%s</%s>%s''' % (prefix, elementName, obj, elementName, newline) | |
637 | elif isinstance(obj, list): | |
638 | if len(obj) < 1: | |
bbf7159c | 639 | #print "marshal: skipping an empty list" |
1f780e48 RD |
640 | return '' |
641 | xmlString = '%s<%s objtype="list">%s' % (prefix, elementName, newline) | |
642 | for item in obj: | |
bbf7159c | 643 | xmlString += marshal(item, nameSpaces=nameSpaces, indent=indent+increment, knownTypes=knownTypes) |
1f780e48 | 644 | xmlString += '%s</%s>%s' % (prefix, elementName, newline) |
bbf7159c | 645 | #print "marshal: returning a list element: \n", xmlString |
1f780e48 RD |
646 | return xmlString |
647 | elif isinstance(obj, tuple): | |
648 | if len(obj) < 1: | |
bbf7159c | 649 | #print "marshal: skipping an empty tuple" |
1f780e48 RD |
650 | return '' |
651 | xmlString = '%s<%s objtype="list" mutable="false">%s' % (prefix, elementName, newline) | |
652 | for item in obj: | |
bbf7159c | 653 | xmlString += marshal(item, nameSpaces=nameSpaces, indent=indent+increment, knownTypes=knownTypes) |
1f780e48 | 654 | xmlString += '%s</%s>%s' % (prefix, elementName, newline) |
bbf7159c | 655 | #print "marshal: returning a tuple element: \n", xmlString |
1f780e48 RD |
656 | return xmlString |
657 | elif isinstance(obj, dict): | |
658 | xmlString = '%s<%s objtype="dict">%s' % (prefix, elementName, newline) | |
659 | subprefix = prefix + ' '*increment | |
660 | subindent = indent + 2*increment | |
661 | for key, val in obj.iteritems(): | |
662 | xmlString += "%s<key>%s%s%s</key>%s%s<value>%s%s%s</value>%s" \ | |
bbf7159c | 663 | % (subprefix, newline, marshal(key, indent=subindent, knownTypes=knownTypes), subprefix, newline, subprefix, newline, marshal(val, nameSpaces=nameSpaces, indent=subindent, knownTypes=knownTypes), subprefix, newline) |
1f780e48 | 664 | xmlString += '%s</%s>%s' % (prefix, elementName, newline) |
bbf7159c | 665 | #print "marshal: returning a dict element: \n", xmlString |
1f780e48 RD |
666 | return xmlString |
667 | else: | |
668 | moduleName = obj.__class__.__module__ | |
669 | if (moduleName == "activegrid.model.schema"): | |
bbf7159c | 670 | ## print "marshal: found an activegrid.model.schema class element" |
1f780e48 RD |
671 | xmlString = '%s<%s%s%s' % (prefix, elementName, nameSpaceAttrs, objattrs) |
672 | else: | |
bbf7159c RD |
673 | ## print "marshal: found a ", moduleName, " class element" |
674 | # Only add the objtype if the element tag is unknown to us. | |
675 | try: | |
676 | objname = knownTypes[elementName] | |
677 | ## print "successfully mapped ", elementName, " to known-objtype ", objname | |
678 | xmlString = '%s<%s%s%s' % (prefix, elementName, nameSpaceAttrs, objattrs) | |
679 | except KeyError: | |
680 | ## print "failed to map elementName: ", elementName, "; knownTypes: ", knownTypes | |
681 | xmlString = '%s<%s%s%s objtype="%s.%s"' % (prefix, elementName, nameSpaceAttrs, objattrs, moduleName, className) | |
682 | ## print "UnknownTypeException: Unknown type (%s.%s) passed to marshaller" % (moduleName, className) | |
1f780e48 RD |
683 | # get the member, value pairs for the object, filtering out |
684 | # the types we don't support. | |
bbf7159c RD |
685 | ## print "marshal: elementString: \n", xmlString |
686 | if (elementAdd != None): | |
687 | prefix += increment*' ' | |
688 | indent += increment | |
689 | ||
1f780e48 RD |
690 | xmlMemberString = '' |
691 | if hasattr(obj, '__xmlbody__'): | |
692 | xmlMemberString = getattr(obj, obj.__xmlbody__) | |
693 | else: | |
694 | entryList = obj.__dict__.items() | |
695 | ## # Add in properties | |
696 | ## for key in obj.__class__.__dict__.iterkeys(): | |
697 | ## if (key not in members_to_skip and key not in obj.__dict__ | |
698 | ## and hasPropertyValue(obj, key)): | |
699 | ## value = getattr(obj, key) | |
700 | ## entryList.append((key, value)) | |
701 | entryList.sort() | |
bbf7159c RD |
702 | if hasattr(obj, '__xmlattrgroups__'): |
703 | attrGroups = obj.__xmlattrgroups__ | |
704 | if (not isinstance(attrGroups,dict)): | |
705 | raise "__xmlattrgroups__ is not a dict, but must be" | |
706 | for n in attrGroups: | |
707 | v = attrGroups[n] | |
708 | members_to_skip += v | |
709 | else: | |
710 | attrGroups = {} | |
711 | # add the list of all attributes to attrGroups | |
712 | eList = [] | |
713 | for x, z in entryList: | |
714 | eList.append(x) | |
715 | attrGroups['__nogroup__'] = eList | |
716 | ||
717 | for eName in attrGroups: | |
718 | eList = attrGroups[eName] | |
719 | if (eName != '__nogroup__'): | |
720 | prefix += increment*' ' | |
721 | indent += increment | |
722 | xmlMemberString += '%s<%s objtype="None">%s' % (prefix, eName, newline) | |
723 | for name in eList: | |
724 | value = obj.__dict__[name] | |
725 | ## print " ", name, " = ", value | |
726 | ## # special name handling for private "__*" attributes: | |
727 | ## # remove the _<class-name> added by Python | |
728 | ## if name.startswith(classNamePrefix): name = name[len(classNamePrefix):] | |
729 | if eName == '__nogroup__' and name in members_to_skip: continue | |
730 | if name.startswith('__') and name.endswith('__'): continue | |
731 | ## idx = name.find('__') | |
732 | ## if idx > 0: | |
733 | ## newName = name[idx+2:] | |
734 | ## if newName: | |
735 | ## name = newName | |
736 | ## print "marshal: processing subElement ", name | |
737 | subElementNameSpacePrefix = nameSpacePrefix | |
738 | if hasattr(obj, '__xmlattrnamespaces__'): | |
739 | for nameSpaceKey, nameSpaceValues in getattr(obj, '__xmlattrnamespaces__').items(): | |
740 | if name in nameSpaceValues: | |
741 | subElementNameSpacePrefix = nameSpaceKey + ':' | |
742 | break | |
743 | # handle sequences listed in __xmlflattensequence__ | |
744 | # specially: instead of listing the contained items inside | |
745 | # of a separate list, as god intended, list them inside | |
746 | # the object containing the sequence. | |
747 | if hasattr(obj, '__xmlflattensequence__') and name in obj.__xmlflattensequence__ and value: | |
748 | try: | |
749 | xmlnametuple = obj.__xmlflattensequence__[name] | |
750 | xmlname = None | |
751 | if len(xmlnametuple) == 1: | |
752 | xmlname = xmlnametuple[0] | |
753 | except: | |
754 | xmlname = name | |
755 | ## xmlname = name.lower() | |
756 | for seqitem in value: | |
757 | xmlMemberString += marshal(seqitem, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment, knownTypes=knownTypes) | |
1f780e48 | 758 | else: |
bbf7159c RD |
759 | if (hasattr(obj, "__xmlrename__") and name in obj.__xmlrename__): |
760 | xmlname = obj.__xmlrename__[name] | |
761 | else: | |
762 | xmlname = name | |
763 | ## xmlname = name.lower() | |
764 | ## # skip | |
765 | ## if xmlname.startswith('_') and not xmlname.startswith('__'): | |
766 | ## xmlname = xmlname[1:] | |
767 | ## if (indent > 30): | |
768 | ## print "getting pretty deep, xmlname = ", xmlname | |
769 | ## print "marshal: marshalling ", xmlname | |
770 | xmlMemberString += marshal(value, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment, knownTypes=knownTypes) | |
771 | ## print "marshal: back with new xmlMemberString: \n", xmlMemberString | |
772 | if (eName != '__nogroup__'): | |
773 | ## print "marshal: Completing attrGroup ", eName | |
774 | xmlMemberString += '%s</%s>%s' % (prefix, eName, newline) | |
775 | prefix = prefix[:-increment] | |
776 | indent -= increment | |
777 | ||
1f780e48 RD |
778 | # if we have nested elements, add them here, otherwise close the element tag immediately. |
779 | if xmlMemberString: | |
780 | xmlString += '>' | |
781 | if hasattr(obj, '__xmlbody__'): | |
782 | xmlString += xmlMemberString | |
783 | xmlString += '</%s>%s' % (elementName, newline) | |
784 | else: | |
785 | xmlString += newline | |
bbf7159c RD |
786 | if (elementAdd != None): |
787 | xmlString += '%s<%s>%s' % (prefix, elementAdd, newline) | |
1f780e48 | 788 | xmlString += xmlMemberString |
bbf7159c RD |
789 | if (elementAdd != None): |
790 | xmlString += '%s</%s>%s' % (prefix, elementAdd, newline) | |
791 | prefix = prefix[:-increment] | |
792 | indent -= increment | |
1f780e48 RD |
793 | xmlString += '%s</%s>%s' % (prefix, elementName, newline) |
794 | else: | |
795 | xmlString = xmlString + '/>%s' % newline | |
796 | return xmlString | |
797 | ||
798 | # We don't use this anymore but in case we want to get properties this is how | |
799 | # you do it | |
800 | def hasPropertyValue(obj, attr): | |
801 | hasProp = False | |
802 | try: | |
803 | prop = obj.__class__.__dict__[attr] | |
804 | if (isinstance(prop, property)): | |
805 | hasProp = hasattr(obj, attr) | |
806 | if (hasProp): | |
807 | # It's a property and it has a value but sometimes we don't want it. | |
808 | # If there is a _hasattr method execute it and the | |
809 | # result will tell us whether to include this value | |
810 | try: | |
811 | hasProp = obj._hasattr(attr) | |
812 | except: | |
813 | pass | |
814 | except KeyError: | |
815 | pass | |
816 | return hasProp | |
817 | ||
818 | if __name__ == '__main__': | |
819 | from xmlmarshallertests import Person, marshalledint, marshalledlist | |
820 | ||
821 | l = [1, 2, 3] | |
822 | d = {'1': 1, '2': 2} | |
823 | outerlist = [l] | |
824 | xmlstr = marshal(d, "d", prettyPrint=True) | |
825 | print xmlstr | |
826 | ||
827 | person = Person() | |
828 | person.firstName = "Albert" | |
829 | person.lastName = "Camus" | |
830 | person.addressLine1 = "23 Absurd St." | |
831 | person.city = "Ennui" | |
832 | person.state = "MO" | |
833 | person.zip = "54321" | |
834 | person._phoneNumber = "808-303-2323" | |
835 | person.favoriteWords = ['angst', 'ennui', 'existence'] | |
836 | person.weight = 150 | |
837 | ||
838 | xmlstring = marshal(person, 'person', prettyPrint=True) | |
839 | print xmlstring | |
840 | ||
841 | obj = unmarshal(marshalledlist) | |
842 | print "obj has type %s and value %s" % (type(obj), str(obj)) | |
843 | for item in obj: | |
844 | print "item: %s" % str(item) |