]>
Commit | Line | Data |
---|---|---|
1f780e48 RD |
1 | #---------------------------------------------------------------------------- |
2 | # Name: xmlmarshaller.py | |
3 | # Purpose: | |
4 | # | |
aca310e5 | 5 | # Authors: John Spurling, Joel Hare, Jeff Norton, Alan Mullendore |
1f780e48 RD |
6 | # |
7 | # Created: 7/28/04 | |
8 | # CVS-ID: $Id$ | |
9 | # Copyright: (c) 2004-2005 ActiveGrid, Inc. | |
10 | # License: wxWindows License | |
11 | #---------------------------------------------------------------------------- | |
6f1a3f9c | 12 | import sys |
1f780e48 | 13 | from types import * |
02b800ce | 14 | from activegrid.util.lang import * |
2eeaec19 | 15 | import logging |
02b800ce | 16 | ifDefPy() |
1f780e48 RD |
17 | import xml.sax |
18 | import xml.sax.handler | |
aca310e5 RD |
19 | import xml.sax.saxutils |
20 | import datetime | |
02b800ce | 21 | endIfDef() |
aca310e5 | 22 | import activegrid.util.utillang as utillang |
02b800ce | 23 | import activegrid.util.objutils as objutils |
aca310e5 | 24 | import activegrid.util.sysutils as sysutils |
2eeaec19 | 25 | import activegrid.util.aglogging as aglogging |
6f1a3f9c RD |
26 | |
27 | MODULE_PATH = "__main__" | |
28 | ||
2eeaec19 | 29 | ## ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed |
aca310e5 | 30 | ##unboundedVal = 2147483647 # value used for maxOccurs == "unbounded" |
1f780e48 RD |
31 | |
32 | """ | |
2eeaec19 | 33 | Special attributes that we recognize: |
1f780e48 RD |
34 | |
35 | name: __xmlname__ | |
36 | type: string | |
37 | description: the name of the xml element for the marshalled object | |
38 | ||
39 | name: __xmlattributes__ | |
40 | type: tuple or list | |
2eeaec19 | 41 | description: the name(s) of the Lang string attribute(s) to be |
1f780e48 | 42 | marshalled as xml attributes instead of nested xml elements. currently |
2eeaec19 | 43 | these can only be strings since there"s not a way to get the type |
1f780e48 RD |
44 | information back when unmarshalling. |
45 | ||
46 | name: __xmlexclude__ | |
47 | type: tuple or list | |
2eeaec19 | 48 | description: the name(s) of the lang attribute(s) to skip when |
1f780e48 RD |
49 | marshalling. |
50 | ||
51 | name: __xmlrename__ | |
52 | type: dict | |
2eeaec19 | 53 | description: describes an alternate Lang <-> XML name mapping. |
1f780e48 | 54 | Normally the name mapping is the identity function. __xmlrename__ |
2eeaec19 | 55 | overrides that. The keys are the Lang names, the values are their |
1f780e48 RD |
56 | associated XML names. |
57 | ||
58 | name: __xmlflattensequence__ | |
59 | type: dict, tuple, or list | |
2eeaec19 | 60 | description: the name(s) of the Lang sequence attribute(s) whose |
1f780e48 RD |
61 | items are to be marshalled as a series of xml elements (with an |
62 | optional keyword argument that specifies the element name to use) as | |
63 | opposed to containing them in a separate sequence element, e.g.: | |
64 | ||
65 | myseq = (1, 2) | |
66 | <!-- normal way of marshalling --> | |
67 | <myseq> | |
2eeaec19 RD |
68 | <item objtype="int">1</item> |
69 | <item objtype="int">2</item> | |
1f780e48 | 70 | </myseq> |
2eeaec19 RD |
71 | <!-- with __xmlflattensequence__ set to {"myseq": "squish"} --> |
72 | <squish objtype="int">1</squish> | |
73 | <squish objtype="int">2</squish> | |
1f780e48 RD |
74 | |
75 | name: __xmlnamespaces__ | |
76 | type: dict | |
77 | description: a dict of the namespaces that the object uses. Each item | |
78 | in the dict should consist of a prefix,url combination where the key is | |
79 | the prefix and url is the value, e.g.: | |
80 | ||
81 | __xmlnamespaces__ = { "xsd":"http://www.w3c.org/foo.xsd" } | |
82 | ||
83 | name: __xmldefaultnamespace__ | |
84 | type: String | |
85 | description: the prefix of a namespace defined in __xmlnamespaces__ that | |
86 | should be used as the default namespace for the object. | |
87 | ||
88 | name: __xmlattrnamespaces__ | |
89 | type: dict | |
2eeaec19 | 90 | description: a dict assigning the Lang object"s attributes to the namespaces |
1f780e48 RD |
91 | defined in __xmlnamespaces__. Each item in the dict should consist of a |
92 | prefix,attributeList combination where the key is the prefix and the value is | |
2eeaec19 | 93 | a list of the Lang attribute names. e.g.: |
1f780e48 RD |
94 | |
95 | __xmlattrnamespaces__ = { "ag":["firstName", "lastName", "addressLine1", "city"] } | |
96 | ||
bbf7159c RD |
97 | name: __xmlattrgroups__ |
98 | type: dict | |
99 | description: a dict specifying groups of attributes to be wrapped in an enclosing tag. | |
100 | The key is the name of the enclosing tag; the value is a list of attributes to include | |
101 | within it. e.g. | |
102 | ||
103 | __xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]} | |
1f780e48 | 104 | |
02b800ce RD |
105 | name: __xmlcdatacontent__ |
106 | type: string | |
107 | description: value is the name of a string attribute that should be assigned CDATA content from the | |
108 | source document and that should be marshalled as CDATA. | |
109 | ||
110 | __xmlcdatacontent__ = "messyContent" | |
111 | ||
1f780e48 RD |
112 | """ |
113 | ||
2eeaec19 RD |
114 | global xmlMarshallerLogger |
115 | xmlMarshallerLogger = logging.getLogger("activegrid.util.xmlmarshaller.marshal") | |
2eeaec19 RD |
116 | # INFO : low-level info |
117 | # DEBUG : debugging info | |
118 | ||
1f780e48 RD |
119 | ################################################################################ |
120 | # | |
121 | # module exceptions | |
122 | # | |
123 | ################################################################################ | |
124 | ||
125 | class Error(Exception): | |
126 | """Base class for errors in this module.""" | |
127 | pass | |
128 | ||
129 | class UnhandledTypeException(Error): | |
130 | """Exception raised when attempting to marshal an unsupported | |
131 | type. | |
132 | """ | |
133 | def __init__(self, typename): | |
134 | self.typename = typename | |
135 | def __str__(self): | |
136 | return "%s is not supported for marshalling." % str(self.typename) | |
137 | ||
138 | class XMLAttributeIsNotStringType(Error): | |
2eeaec19 | 139 | """Exception raised when an object"s attribute is specified to be |
1f780e48 RD |
140 | marshalled as an XML attribute of the enclosing object instead of |
141 | a nested element. | |
142 | """ | |
143 | def __init__(self, attrname, typename): | |
144 | self.attrname = attrname | |
145 | self.typename = typename | |
146 | def __str__(self): | |
147 | return """%s was set to be marshalled as an XML attribute | |
2eeaec19 | 148 | instead of a nested element, but the object"s type is %s, not |
1f780e48 RD |
149 | string.""" % (self.attrname, self.typename) |
150 | ||
2eeaec19 | 151 | class MarshallerException(Exception): |
02b800ce RD |
152 | pass |
153 | ||
154 | class UnmarshallerException(Exception): | |
2eeaec19 RD |
155 | pass |
156 | ||
1f780e48 RD |
157 | ################################################################################ |
158 | # | |
159 | # constants and such | |
160 | # | |
161 | ################################################################################ | |
162 | ||
2eeaec19 RD |
163 | XMLNS = "xmlns" |
164 | XMLNS_PREFIX = XMLNS + ":" | |
1f780e48 | 165 | XMLNS_PREFIX_LENGTH = len(XMLNS_PREFIX) |
02b800ce RD |
166 | DEFAULT_NAMESPACE_KEY = "__DEFAULTNS__" |
167 | TYPE_QNAME = "QName" | |
168 | XMLSCHEMA_XSD_URL = "http://www.w3.org/2001/XMLSchema" | |
169 | AG_URL = "http://www.activegrid.com/ag.xsd" | |
1f780e48 | 170 | |
2eeaec19 RD |
171 | BASETYPE_ELEMENT_NAME = "item" |
172 | DICT_ITEM_NAME = "qqDictItem" | |
173 | DICT_ITEM_KEY_NAME = "key" | |
174 | DICT_ITEM_VALUE_NAME = "value" | |
bbf7159c | 175 | |
2eeaec19 | 176 | # This list doesn"t seem to be used. |
bbf7159c | 177 | # Internal documentation or useless? You make the call! |
2eeaec19 RD |
178 | ##MEMBERS_TO_SKIP = ("__module__", "__doc__", "__xmlname__", "__xmlattributes__", |
179 | ## "__xmlexclude__", "__xmlflattensequence__", "__xmlnamespaces__", | |
180 | ## "__xmldefaultnamespace__", "__xmlattrnamespaces__", | |
181 | ## "__xmlattrgroups__") | |
bbf7159c | 182 | |
1f780e48 RD |
183 | ################################################################################ |
184 | # | |
185 | # classes and functions | |
186 | # | |
187 | ################################################################################ | |
188 | ||
2eeaec19 | 189 | def setattrignorecase(object, name, value): |
aca310e5 | 190 | ## print "[setattrignorecase] name = %s, value = %s" % (name, value) |
2eeaec19 RD |
191 | if (name not in object.__dict__): |
192 | namelow = name.lower() | |
193 | for attr in object.__dict__: | |
194 | if attr.lower() == namelow: | |
195 | object.__dict__[attr] = value | |
196 | return | |
197 | object.__dict__[name] = value | |
198 | ||
199 | def getComplexType(obj): | |
aca310e5 RD |
200 | if (hasattr(obj, "_instancexsdcomplextype")): |
201 | return obj._instancexsdcomplextype | |
2eeaec19 RD |
202 | if (hasattr(obj, "__xsdcomplextype__")): |
203 | return obj.__xsdcomplextype__ | |
204 | return None | |
205 | ||
aca310e5 RD |
206 | def _objectfactory(objtype, objargs=None, objclass=None): |
207 | "dynamically create an object based on the objtype and return it." | |
6f1a3f9c RD |
208 | if not isinstance(objargs, list): |
209 | objargs = [objargs] | |
02b800ce | 210 | if (objclass != None): |
aca310e5 | 211 | obj = None |
02b800ce RD |
212 | if (len(objargs) > 0): |
213 | if (hasattr(objclass, "__xmlcdatacontent__")): | |
214 | obj = objclass() | |
215 | contentAttr = obj.__xmlcdatacontent__ | |
216 | obj.__dict__[contentAttr] = str(objargs[0]) | |
aca310e5 RD |
217 | else: |
218 | obj = objclass(*objargs) | |
1f780e48 | 219 | else: |
aca310e5 RD |
220 | obj = objclass() |
221 | if ((obj != None) and (hasattr(obj, 'postUnmarshal'))): | |
222 | obj.postUnmarshal() | |
223 | return obj | |
224 | return objutils.newInstance(objtype, objargs) | |
225 | ||
226 | class GenericXMLObject(object): | |
227 | def __init__(self, content=None): | |
228 | if content != None: | |
229 | self._content = content | |
230 | self.__xmlcontent__ = '_content' | |
231 | ||
232 | def __str__(self): | |
233 | return "GenericXMLObject(%s)" % objutils.toDiffableString(self.__dict__) | |
1f780e48 | 234 | |
aca310e5 RD |
235 | def setXMLAttributes(self, xmlName, attrs=None, children=None, nsMap=None, defaultNS=None): |
236 | if xmlName != None: | |
237 | i = xmlName.rfind(':') | |
238 | if i < 0: | |
239 | self.__xmlname__ = xmlName | |
240 | if defaultNS != None: | |
241 | self.__xmldefaultnamespace__ = str(defaultNS) | |
242 | else: | |
243 | self.__xmlname__ = xmlName[i+1:] | |
244 | prefix = xmlName[:i] | |
245 | if nsMap.has_key(prefix): | |
246 | self.__xmldefaultnamespace__ = str(nsMap[prefix]) | |
247 | if attrs != None: | |
248 | for attrname, attr in attrs.items(): | |
249 | attrname = str(attrname) | |
250 | if attrname == XMLNS or attrname.startswith(XMLNS_PREFIX): | |
251 | pass | |
252 | elif attrname == "objtype": | |
253 | pass | |
254 | else: | |
255 | if not hasattr(self, '__xmlattributes__'): | |
256 | self.__xmlattributes__ = [] | |
257 | i = attrname.rfind(':') | |
258 | if i >= 0: | |
259 | prefix = attrname[:i] | |
260 | attrname = attrname[i+1:] | |
261 | if not hasattr(self, '__xmlattrnamespaces__'): | |
262 | self.__xmlattrnamespaces__ = {} | |
263 | if self.__xmlattrnamespaces__.has_key(prefix): | |
264 | alist = self.__xmlattrnamespaces__[prefix] | |
265 | else: | |
266 | alist = [] | |
267 | alist.append(attrname) | |
268 | self.__xmlattrnamespaces__[prefix] = alist | |
269 | self.__xmlattributes__.append(attrname) | |
270 | if hasattr(self, '__xmlattributes__'): | |
271 | self.__xmlattributes__.sort() | |
272 | if children != None and len(children) > 0: | |
273 | childList = [] | |
274 | flattenList = {} | |
275 | for childname, child in children: | |
276 | childstr = str(childname) | |
277 | if childstr in childList: | |
278 | if not flattenList.has_key(childstr): | |
279 | flattenList[childstr] = (childstr,) | |
280 | else: | |
281 | childList.append(childstr) | |
282 | if len(flattenList) > 0: | |
283 | self.__xmlflattensequence__ = flattenList | |
284 | ||
285 | def initialize(self, arg1=None): | |
286 | pass | |
287 | ||
288 | ||
1f780e48 | 289 | class Element: |
02b800ce | 290 | def __init__(self, name, attrs=None, xsname=None): |
1f780e48 RD |
291 | self.name = name |
292 | self.attrs = attrs | |
2eeaec19 | 293 | self.content = "" |
1f780e48 | 294 | self.children = [] |
02b800ce RD |
295 | self.objclass = None |
296 | self.xsname = xsname | |
aca310e5 | 297 | self.objtype = None |
2eeaec19 | 298 | |
1f780e48 | 299 | def getobjtype(self): |
aca310e5 RD |
300 | # objtype = self.attrs.get("objtype") |
301 | objtype = self.objtype | |
2eeaec19 RD |
302 | if (objtype == None): |
303 | if (len(self.children) > 0): | |
304 | objtype = "dict" | |
305 | else: | |
306 | objtype = "str" | |
307 | return objtype | |
308 | ||
02b800ce RD |
309 | class NsElement(object): |
310 | def __init__(self): | |
311 | self.nsMap = {} | |
312 | self.targetNS = None | |
313 | self.defaultNS = None | |
314 | self.prefix = None | |
02b800ce | 315 | |
aca310e5 RD |
316 | def __str__(self): |
317 | if self.prefix == None: | |
318 | strVal = 'prefix = None; ' | |
319 | else: | |
320 | strVal = 'prefix = "%s"; ' % (self.prefix) | |
321 | if self.targetNS == None: | |
322 | strVal += 'targetNS = None; ' | |
323 | else: | |
324 | strVal += 'targetNS = "%s"; ' % (self.targetNS) | |
325 | if self.defaultNS == None: | |
326 | strVal += 'defaultNS = None; ' | |
327 | else: | |
328 | strVal += 'defaultNS = "%s"; ' % (self.defaultNS) | |
329 | if len(self.nsMap) == 0: | |
330 | strVal += 'nsMap = None; ' | |
331 | else: | |
332 | strVal += 'nsMap = {' | |
333 | for ik, iv in self.nsMap.iteritems(): | |
334 | strVal += '%s=%s; ' % (ik,iv) | |
335 | strVal += '}' | |
336 | return strVal | |
337 | ||
02b800ce RD |
338 | def setKnownTypes(self, masterKnownTypes, masterKnownNamespaces, parentNSE): |
339 | # if we're a nested element, extend our parent element's mapping | |
340 | if parentNSE != None: | |
341 | self.knownTypes = parentNSE.knownTypes.copy() | |
342 | # but if we have a different default namespace, replace the parent's default mappings | |
aca310e5 | 343 | if (self.defaultNS != None) and (parentNSE.defaultNS != self.defaultNS): |
02b800ce RD |
344 | newKT = self.knownTypes.copy() |
345 | for tag in newKT: | |
346 | if tag.find(':') < 0: | |
347 | del self.knownTypes[tag] | |
348 | newMap = parentNSE.nsMap.copy() | |
349 | if self.nsMap != {}: | |
350 | for k, v in self.nsMap.iteritems(): | |
351 | newMap[k] = v | |
352 | self.nsMap = newMap | |
353 | else: | |
354 | self.knownTypes = {} | |
355 | reversedKNS = {} | |
356 | # TODO: instead of starting with the knownNamespaces, start with the "xmlms" mappings | |
357 | # for this element. Then we'd only process the namespaces and tags we need to. | |
358 | # But for now, this works. | |
359 | for long, short in masterKnownNamespaces.iteritems(): | |
360 | reversedKNS[short] = long | |
361 | mapLongs = self.nsMap.values() | |
362 | for tag, mapClass in masterKnownTypes.iteritems(): | |
363 | i = tag.rfind(':') | |
364 | if i >= 0: # e.g. "wsdl:description" | |
365 | knownTagShort = tag[:i] # "wsdl" | |
366 | knownTagName = tag[i+1:] # "description" | |
367 | knownTagLong = reversedKNS[knownTagShort] # e.g. "http://schemas.xmlsoap.org/wsdl" | |
368 | if (knownTagLong in mapLongs): | |
369 | for mShort, mLong in self.nsMap.iteritems(): | |
370 | if mLong == knownTagLong: | |
371 | actualShort = mShort # e.g. "ws" | |
372 | actualTag = '%s:%s' % (actualShort, knownTagName) | |
373 | self.knownTypes[actualTag] = mapClass | |
374 | break | |
375 | if self.defaultNS == knownTagLong: | |
376 | self.knownTypes[knownTagName] = mapClass | |
377 | else: # e.g. "ItemSearchRequest" | |
378 | self.knownTypes[tag] = mapClass | |
02b800ce RD |
379 | |
380 | def expandQName(self, eName, attrName, attrValue): | |
381 | bigValue = attrValue | |
382 | i = attrValue.rfind(':') | |
383 | if (i < 0): | |
384 | if self.defaultNS != None: | |
385 | bigValue = '%s:%s' % (self.defaultNS, attrValue) | |
386 | else: | |
387 | attrNS = attrValue[:i] | |
388 | attrNCName = attrValue[i+1:] | |
389 | for shortNs, longNs in self.nsMap.iteritems(): | |
390 | if shortNs == attrNS: | |
391 | bigValue = '%s:%s' % (longNs, attrNCName) | |
392 | break | |
02b800ce | 393 | return bigValue |
1f780e48 RD |
394 | |
395 | class XMLObjectFactory(xml.sax.ContentHandler): | |
aca310e5 | 396 | def __init__(self, knownTypes=None, knownNamespaces=None, xmlSource=None, createGenerics=False): |
1f780e48 | 397 | self.rootelement = None |
aca310e5 RD |
398 | if xmlSource == None: |
399 | self.xmlSource = "unknown" | |
02b800ce | 400 | else: |
aca310e5 RD |
401 | self.xmlSource = xmlSource |
402 | self.createGenerics = createGenerics | |
02b800ce | 403 | self.skipper = False |
1f780e48 | 404 | self.elementstack = [] |
02b800ce RD |
405 | self.nsstack = [] |
406 | self.collectContent = None | |
aca310e5 RD |
407 | if (knownNamespaces == None): |
408 | self.knownNamespaces = {} | |
409 | else: | |
410 | self.knownNamespaces = knownNamespaces | |
411 | self.reversedNamespaces = {} | |
412 | for longns, shortns in self.knownNamespaces.iteritems(): | |
413 | self.reversedNamespaces[shortns] = longns | |
414 | self.knownTypes = {} | |
415 | if (knownTypes != None): | |
416 | for tag, cls in knownTypes.iteritems(): | |
417 | i = tag.rfind(':') | |
418 | if i >= 0: | |
419 | shortns = tag[:i] | |
420 | tag = tag[i+1:] | |
421 | if not self.reversedNamespaces.has_key(shortns): | |
422 | errorString = 'Error unmarshalling XML document from source "%s": knownTypes specifies an unmapped short namespace "%s" for element "%s"' % (self.xmlSource, shortns, tag) | |
423 | raise UnmarshallerException(errorString) | |
424 | longns = self.reversedNamespaces[shortns] | |
425 | tag = '%s:%s' % (longns, tag) | |
426 | self.knownTypes[tag] = cls | |
427 | #printKnownTypes(self.knownTypes, 'Unmarshaller.XMLObjectFactory.__init__') | |
1f780e48 RD |
428 | xml.sax.handler.ContentHandler.__init__(self) |
429 | ||
02b800ce RD |
430 | def appendElementStack(self, newElement, newNS): |
431 | self.elementstack.append(newElement) | |
aca310e5 RD |
432 | if (len(self.nsstack) > 0): |
433 | oldNS = self.nsstack[-1] | |
434 | if newNS.defaultNS == None: | |
435 | newNS.defaultNS = oldNS.defaultNS | |
436 | if newNS.targetNS == None: | |
437 | newNS.targetNS = oldNS.targetNS | |
438 | if len(newNS.nsMap) == 0: | |
439 | newNS.nsMap = oldNS.nsMap | |
440 | elif len(oldNS.nsMap) > 0: | |
441 | map = oldNS.nsMap.copy() | |
442 | map.update(newNS.nsMap) | |
443 | newNS.nsMap = map | |
02b800ce RD |
444 | self.nsstack.append(newNS) |
445 | return newNS | |
446 | ||
447 | def popElementStack(self): | |
448 | element = self.elementstack.pop() | |
449 | nse = self.nsstack.pop() | |
450 | return element, nse | |
bbf7159c | 451 | |
1f780e48 RD |
452 | ## ContentHandler methods |
453 | def startElement(self, name, attrs): | |
02b800ce RD |
454 | ## print '[startElement] <%s>' % (name) |
455 | if name == 'xs:annotation' or name == 'xsd:annotation': # should use namespace mapping here | |
456 | self.skipper = True | |
457 | self.appendElementStack(Element(name, attrs.copy()), NsElement()) | |
458 | if self.skipper: | |
459 | return | |
460 | if self.collectContent != None: | |
461 | strVal = '<%s' % (name) | |
462 | for aKey, aVal in attrs.items(): | |
463 | strVal += (' %s="%s"' % (aKey, aVal)) | |
464 | strVal += '>' | |
465 | self.collectContent.content += strVal | |
466 | xsname = name | |
aca310e5 RD |
467 | i = name.rfind(':') |
468 | if i >= 0: | |
469 | nsname = name[:i] | |
470 | name = name[i+1:] | |
471 | else: | |
472 | nsname = None | |
02b800ce RD |
473 | element = Element(name, attrs.copy(), xsname=xsname) |
474 | # if the element has namespace attributes, process them and add them to our stack | |
475 | nse = NsElement() | |
aca310e5 | 476 | objtype = None |
02b800ce RD |
477 | for k in attrs.getNames(): |
478 | if k.startswith('xmlns'): | |
479 | longNs = attrs[k] | |
480 | eLongNs = longNs + '/' | |
481 | if str(eLongNs) in asDict(self.knownNamespaces): | |
482 | longNs = eLongNs | |
483 | if k == 'xmlns': | |
484 | nse.defaultNS = longNs | |
485 | else: | |
486 | shortNs = k[6:] | |
487 | nse.nsMap[shortNs] = longNs | |
488 | elif k == 'targetNamespace': | |
489 | nse.targetNS = attrs.getValue(k) | |
aca310e5 RD |
490 | elif k == 'objtype': |
491 | objtype = attrs.getValue(k) | |
02b800ce | 492 | nse = self.appendElementStack(element, nse) |
aca310e5 RD |
493 | if nsname != None: |
494 | if nse.nsMap.has_key(nsname): | |
495 | longname = '%s:%s' % (nse.nsMap[nsname], name) | |
496 | ## elif objtype == None: | |
497 | ## errorString = 'Error unmarshalling XML document from source "%s": tag "%s" at line "%d", column "%d" has an undefined namespace' % (self.xmlSource, xsname, self._locator.getLineNumber(), self._locator.getColumnNumber()) | |
498 | ## raise UnmarshallerException(errorString) | |
499 | elif self.reversedNamespaces.has_key(nsname): | |
500 | longname = '%s:%s' % (self.reversedNamespaces[nsname], name) | |
501 | else: | |
502 | longname = xsname | |
503 | elif nse.defaultNS != None: | |
504 | longname = '%s:%s' % (nse.defaultNS, name) | |
505 | else: | |
506 | longname = name | |
507 | element.objtype = objtype | |
508 | element.objclass = self.knownTypes.get(longname) | |
509 | if element.objclass == None and len(self.knownNamespaces) == 0: | |
510 | # handles common case where tags are unqualified and knownTypes are too, but there's a defaultNS | |
511 | element.objclass = self.knownTypes.get(name) | |
02b800ce RD |
512 | if (hasattr(element.objclass, "__xmlcontent__")): |
513 | self.collectContent = element | |
1f780e48 RD |
514 | |
515 | def characters(self, content): | |
02b800ce | 516 | ## print '[characters] "%s" (%s)' % (content, type(content)) |
2eeaec19 | 517 | if (content != None): |
02b800ce RD |
518 | if self.collectContent != None: |
519 | self.collectContent.content += content | |
520 | else: | |
521 | self.elementstack[-1].content += content | |
1f780e48 RD |
522 | |
523 | def endElement(self, name): | |
02b800ce | 524 | ## print "[endElement] </%s>" % name |
1f780e48 | 525 | xsname = name |
aca310e5 RD |
526 | i = name.rfind(':') |
527 | if i >= 0: # Strip namespace prefixes for now until actually looking them up in xsd | |
528 | name = name[i+1:] | |
02b800ce RD |
529 | if self.skipper: |
530 | if xsname == "xs:annotation" or xsname == "xsd:annotation": # here too | |
531 | self.skipper = False | |
532 | self.popElementStack() | |
533 | return | |
534 | if self.collectContent != None: | |
535 | if xsname != self.collectContent.xsname: | |
536 | self.collectContent.content += ('</%s>' % (xsname)) | |
537 | self.popElementStack() | |
538 | return | |
539 | else: | |
540 | self.collectContent = None | |
bbf7159c | 541 | oldChildren = self.elementstack[-1].children |
02b800ce | 542 | element, nse = self.popElementStack() |
bbf7159c RD |
543 | if ((len(self.elementstack) > 1) and (self.elementstack[-1].getobjtype() == "None")): |
544 | parentElement = self.elementstack[-2] | |
bbf7159c RD |
545 | elif (len(self.elementstack) > 0): |
546 | parentElement = self.elementstack[-1] | |
1f780e48 | 547 | objtype = element.getobjtype() |
bbf7159c | 548 | if (objtype == "None"): |
bbf7159c | 549 | return |
1f780e48 | 550 | constructorarglist = [] |
2eeaec19 | 551 | if (len(element.content) > 0): |
1f780e48 | 552 | strippedElementContent = element.content.strip() |
2eeaec19 | 553 | if (len(strippedElementContent) > 0): |
1f780e48 | 554 | constructorarglist.append(element.content) |
aca310e5 RD |
555 | # If the element requires an object, but none is known, use the GenericXMLObject class |
556 | if ((element.objclass == None) and (element.attrs.get("objtype") == None) and ((len(element.attrs) > 0) or (len(element.children) > 0))): | |
557 | if self.createGenerics: | |
558 | element.objclass = GenericXMLObject | |
02b800ce | 559 | obj = _objectfactory(objtype, constructorarglist, element.objclass) |
aca310e5 RD |
560 | if element.objclass == GenericXMLObject: |
561 | obj.setXMLAttributes(str(xsname), element.attrs, element.children, nse.nsMap, nse.defaultNS) | |
2eeaec19 RD |
562 | complexType = getComplexType(obj) |
563 | if (obj != None): | |
564 | if (hasattr(obj, "__xmlname__") and getattr(obj, "__xmlname__") == "sequence"): | |
bbf7159c | 565 | self.elementstack[-1].children = oldChildren |
bbf7159c | 566 | return |
2eeaec19 | 567 | if (len(element.attrs) > 0) and not isinstance(obj, list): |
1f780e48 RD |
568 | for attrname, attr in element.attrs.items(): |
569 | if attrname == XMLNS or attrname.startswith(XMLNS_PREFIX): | |
570 | if attrname.startswith(XMLNS_PREFIX): | |
571 | ns = attrname[XMLNS_PREFIX_LENGTH:] | |
572 | else: | |
573 | ns = "" | |
aca310e5 | 574 | if complexType != None or element.objclass == GenericXMLObject: |
02b800ce RD |
575 | if not hasattr(obj, "__xmlnamespaces__"): |
576 | obj.__xmlnamespaces__ = {ns:attr} | |
577 | elif ns not in obj.__xmlnamespaces__: | |
578 | if (hasattr(obj.__class__, "__xmlnamespaces__") | |
579 | and (obj.__xmlnamespaces__ is obj.__class__.__xmlnamespaces__)): | |
580 | obj.__xmlnamespaces__ = dict(obj.__xmlnamespaces__) | |
581 | obj.__xmlnamespaces__[ns] = attr | |
2eeaec19 RD |
582 | elif not attrname == "objtype": |
583 | if attrname.find(":") > -1: # Strip namespace prefixes for now until actually looking them up in xsd | |
584 | attrname = attrname[attrname.find(":") + 1:] | |
585 | if (complexType != None): | |
1f780e48 | 586 | xsdElement = complexType.findElement(attrname) |
2eeaec19 | 587 | if (xsdElement != None): |
1f780e48 | 588 | type = xsdElement.type |
2eeaec19 | 589 | if (type != None): |
02b800ce RD |
590 | if (type == TYPE_QNAME): |
591 | attr = nse.expandQName(name, attrname, attr) | |
2eeaec19 | 592 | type = xsdToLangType(type) |
1f780e48 RD |
593 | ### ToDO remove maxOccurs hack after bug 177 is fixed |
594 | if attrname == "maxOccurs" and attr == "unbounded": | |
595 | attr = "-1" | |
aca310e5 RD |
596 | try: |
597 | attr = _objectfactory(type, attr) | |
598 | except Exception, exceptData: | |
599 | errorString = 'Error unmarshalling attribute "%s" at line %d, column %d in XML document from source "%s": %s' % (attrname, self._locator.getLineNumber(), self._locator.getColumnNumber(), self.xmlSource, str(exceptData)) | |
600 | raise UnmarshallerException(errorString) | |
2eeaec19 RD |
601 | try: |
602 | setattrignorecase(obj, _toAttrName(obj, attrname), attr) | |
603 | except AttributeError: | |
aca310e5 | 604 | errorString = 'Error setting value of attribute "%s" at line %d, column %d in XML document from source "%s": object type of XML element "%s" is not specified or known' % (attrname, self._locator.getLineNumber(), self._locator.getColumnNumber(), self.xmlSource, name) |
02b800ce | 605 | raise UnmarshallerException(errorString) |
1f780e48 RD |
606 | ## obj.__dict__[_toAttrName(obj, attrname)] = attr |
607 | # stuff any child attributes meant to be in a sequence via the __xmlflattensequence__ | |
608 | flattenDict = {} | |
2eeaec19 RD |
609 | if hasattr(obj, "__xmlflattensequence__"): |
610 | flatten = obj.__xmlflattensequence__ | |
2eeaec19 | 611 | if (isinstance(flatten, dict)): |
2eeaec19 RD |
612 | for sequencename, xmlnametuple in flatten.items(): |
613 | if (xmlnametuple == None): | |
614 | flattenDict[sequencename] = sequencename | |
615 | elif (not isinstance(xmlnametuple, (tuple, list))): | |
616 | flattenDict[str(xmlnametuple)] = sequencename | |
617 | else: | |
618 | for xmlname in xmlnametuple: | |
2eeaec19 | 619 | flattenDict[xmlname] = sequencename |
bbf7159c | 620 | else: |
02b800ce | 621 | raise Exception("Invalid type for __xmlflattensequence___ : it must be a dict") |
1f780e48 | 622 | |
2eeaec19 | 623 | # reattach an object"s attributes to it |
1f780e48 | 624 | for childname, child in element.children: |
2eeaec19 | 625 | if (childname in flattenDict): |
1f780e48 | 626 | sequencename = _toAttrName(obj, flattenDict[childname]) |
2eeaec19 | 627 | if (not hasattr(obj, sequencename)): |
2eeaec19 RD |
628 | obj.__dict__[sequencename] = [] |
629 | sequencevalue = getattr(obj, sequencename) | |
630 | if (sequencevalue == None): | |
631 | obj.__dict__[sequencename] = [] | |
632 | sequencevalue = getattr(obj, sequencename) | |
1f780e48 | 633 | sequencevalue.append(child) |
2eeaec19 | 634 | elif (objtype == "list"): |
1f780e48 | 635 | obj.append(child) |
2eeaec19 RD |
636 | elif isinstance(obj, dict): |
637 | if (childname == DICT_ITEM_NAME): | |
638 | obj[child[DICT_ITEM_KEY_NAME]] = child[DICT_ITEM_VALUE_NAME] | |
639 | else: | |
640 | obj[childname] = child | |
1f780e48 | 641 | else: |
aca310e5 RD |
642 | # don't replace a good attribute value with a bad one |
643 | childAttrName = _toAttrName(obj, childname) | |
644 | if (not hasattr(obj, childAttrName)) or (getattr(obj, childAttrName) == None) or (getattr(obj, childAttrName) == []) or (not isinstance(child, GenericXMLObject)): | |
645 | try: | |
646 | setattrignorecase(obj, childAttrName, child) | |
647 | except AttributeError: | |
648 | raise MarshallerException("Error unmarshalling child element \"%s\" of XML element \"%s\": object type not specified or known" % (childname, name)) | |
1f780e48 | 649 | |
2eeaec19 | 650 | if (complexType != None): |
1f780e48 RD |
651 | for element in complexType.elements: |
652 | if element.default: | |
653 | elementName = _toAttrName(obj, element.name) | |
654 | if ((elementName not in obj.__dict__) or (obj.__dict__[elementName] == None)): | |
2eeaec19 RD |
655 | langType = xsdToLangType(element.type) |
656 | defaultValue = _objectfactory(langType, element.default) | |
1f780e48 RD |
657 | obj.__dict__[elementName] = defaultValue |
658 | ||
2eeaec19 RD |
659 | ifDefPy() |
660 | if (isinstance(obj, list)): | |
661 | if ((element.attrs.has_key("mutable")) and (element.attrs.getValue("mutable") == "false")): | |
662 | obj = tuple(obj) | |
663 | endIfDef() | |
664 | ||
665 | if (len(self.elementstack) > 0): | |
666 | ## print "[endElement] appending child with name: ", name, "; objtype: ", objtype | |
667 | parentElement.children.append((name, obj)) | |
2eeaec19 RD |
668 | else: |
669 | self.rootelement = obj | |
670 | ||
1f780e48 RD |
671 | def getRootObject(self): |
672 | return self.rootelement | |
673 | ||
674 | def _toAttrName(obj, name): | |
675 | if (hasattr(obj, "__xmlrename__")): | |
676 | for key, val in obj.__xmlrename__.iteritems(): | |
677 | if (name == val): | |
678 | name = key | |
679 | break | |
680 | ## if (name.startswith("__") and not name.endswith("__")): | |
681 | ## name = "_%s%s" % (obj.__class__.__name__, name) | |
aca310e5 RD |
682 | return str(name) |
683 | ||
684 | def printKnownTypes(kt, where): | |
685 | print 'KnownTypes from %s' % (where) | |
686 | for tag, cls in kt.iteritems(): | |
687 | print '%s => %s' % (tag, str(cls)) | |
1f780e48 | 688 | |
2eeaec19 | 689 | __typeMappingXsdToLang = { |
1f780e48 RD |
690 | "string": "str", |
691 | "char": "str", | |
692 | "varchar": "str", | |
2eeaec19 | 693 | "date": "str", # ToDO Need to work out how to create lang date types |
1f780e48 RD |
694 | "boolean": "bool", |
695 | "decimal": "float", # ToDO Does python have a better fixed point type? | |
696 | "int": "int", | |
02b800ce | 697 | "integer":"int", |
1f780e48 RD |
698 | "long": "long", |
699 | "float": "float", | |
700 | "bool": "bool", | |
701 | "str": "str", | |
702 | "unicode": "unicode", | |
bbf7159c RD |
703 | "short": "int", |
704 | "duration": "str", # see above (date) | |
705 | "datetime": "str", # see above (date) | |
706 | "time": "str", # see above (date) | |
707 | "double": "float", | |
02b800ce RD |
708 | "QName" : "str", |
709 | "blob" : "str", # ag:blob | |
710 | "currency" : "str", # ag:currency | |
1f780e48 RD |
711 | } |
712 | ||
2eeaec19 | 713 | def xsdToLangType(xsdType): |
02b800ce RD |
714 | if xsdType.startswith(XMLSCHEMA_XSD_URL): |
715 | xsdType = xsdType[len(XMLSCHEMA_XSD_URL)+1:] | |
716 | elif xsdType.startswith(AG_URL): | |
aca310e5 | 717 | xsdType = xsdType[len(AG_URL)+1:] |
2eeaec19 RD |
718 | langType = __typeMappingXsdToLang.get(xsdType) |
719 | if (langType == None): | |
1f780e48 | 720 | raise Exception("Unknown xsd type %s" % xsdType) |
2eeaec19 | 721 | return langType |
02b800ce RD |
722 | |
723 | def langToXsdType(langType): | |
724 | if langType in asDict(__typeMappingXsdToLang): | |
725 | return '%s:%s' % (XMLSCHEMA_XSD_URL, langType) | |
726 | return langType | |
1f780e48 | 727 | |
2eeaec19 RD |
728 | def _getXmlValue(langValue): |
729 | if (isinstance(langValue, bool)): | |
730 | return str(langValue).lower() | |
731 | elif (isinstance(langValue, unicode)): | |
732 | return langValue.encode() | |
1f780e48 | 733 | else: |
2eeaec19 | 734 | return str(langValue) |
1f780e48 | 735 | |
aca310e5 RD |
736 | def unmarshal(xmlstr, knownTypes=None, knownNamespaces=None, xmlSource=None, createGenerics=False): |
737 | objectfactory = XMLObjectFactory(knownTypes, knownNamespaces, xmlSource, createGenerics) | |
738 | # on Linux, pyXML's sax.parseString fails when passed unicode | |
739 | if (not sysutils.isWindows()): | |
740 | xmlstr = str(xmlstr) | |
02b800ce RD |
741 | try: |
742 | xml.sax.parseString(xmlstr, objectfactory) | |
743 | except xml.sax.SAXParseException, errorData: | |
744 | if xmlSource == None: | |
745 | xmlSource = 'unknown' | |
746 | errorString = 'SAXParseException ("%s") detected at line %d, column %d in XML document from source "%s" ' % (errorData.getMessage(), errorData.getLineNumber(), errorData.getColumnNumber(), xmlSource) | |
747 | raise UnmarshallerException(errorString) | |
1f780e48 RD |
748 | return objectfactory.getRootObject() |
749 | ||
02b800ce | 750 | def marshal(obj, elementName=None, prettyPrint=False, marshalType=True, indent=0, knownTypes=None, knownNamespaces=None, encoding=-1): |
02b800ce RD |
751 | worker = XMLMarshalWorker(prettyPrint=prettyPrint, marshalType=marshalType, knownTypes=knownTypes, knownNamespaces=knownNamespaces) |
752 | if obj != None and hasattr(obj, '__xmldeepexclude__'): | |
753 | worker.xmldeepexclude = obj.__xmldeepexclude__ | |
754 | xmlstr = "".join(worker._marshal(obj, elementName, indent=indent)) | |
aca310e5 RD |
755 | aglogging.info(xmlMarshallerLogger, "marshal produced string of type %s", type(xmlstr)) |
756 | if (encoding == None): | |
6f1a3f9c | 757 | return xmlstr |
aca310e5 RD |
758 | if (not isinstance(encoding, basestring)): |
759 | encoding = sys.getdefaultencoding() | |
760 | if (not isinstance(xmlstr, unicode)): | |
761 | xmlstr = xmlstr.decode() | |
762 | xmlstr = u'<?xml version="1.0" encoding="%s"?>\n%s' % (encoding, xmlstr) | |
763 | return xmlstr.encode(encoding) | |
6f1a3f9c | 764 | |
02b800ce RD |
765 | class XMLMarshalWorker(object): |
766 | def __init__(self, marshalType=True, prettyPrint=False, knownTypes=None, knownNamespaces=None): | |
767 | if knownTypes == None: | |
768 | self.knownTypes = {} | |
1f780e48 | 769 | else: |
02b800ce RD |
770 | self.knownTypes = knownTypes |
771 | if knownNamespaces == None: | |
772 | self.knownNamespaces = {} | |
773 | else: | |
774 | self.knownNamespaces = knownNamespaces | |
775 | self.prettyPrint = prettyPrint | |
776 | self.marshalType = marshalType | |
777 | self.xmldeepexclude = [] | |
778 | self.nsstack = [] | |
779 | ||
780 | def getNSPrefix(self): | |
781 | if len(self.nsstack) > 0: | |
782 | return self.nsstack[-1].prefix | |
783 | return '' | |
784 | ||
785 | def isKnownType(self, elementName): | |
786 | tagLongNs = None | |
787 | nse = self.nsstack[-1] | |
788 | i = elementName.rfind(':') | |
789 | if i > 0: | |
790 | prefix = elementName[:i] | |
791 | name = elementName[i+1:] | |
792 | else: | |
793 | prefix = DEFAULT_NAMESPACE_KEY | |
794 | name = elementName | |
795 | for shortNs, longNs in nse.nameSpaces.iteritems(): | |
796 | if shortNs == prefix: | |
797 | tagLongNs = longNs | |
798 | break | |
799 | if tagLongNs == None: | |
800 | knownTagName = elementName | |
801 | else: | |
802 | knownShortNs = self.knownNamespaces[tagLongNs] | |
803 | knownTagName = knownShortNs + ':' + name | |
804 | if (knownTagName in asDict(self.knownTypes)): | |
805 | knownClass = self.knownTypes[knownTagName] | |
806 | return True | |
807 | return False | |
808 | ||
809 | def popNSStack(self): | |
810 | self.nsstack.pop() | |
811 | ||
812 | def appendNSStack(self, obj): | |
813 | nameSpaces = {} | |
814 | defaultLongNS = None | |
815 | for nse in self.nsstack: | |
816 | for k, v in nse.nsMap.iteritems(): | |
817 | nameSpaces[k] = v | |
818 | if k == DEFAULT_NAMESPACE_KEY: | |
819 | defaultLongNS = v | |
820 | newNS = NsElement() | |
821 | nameSpaceAttrs = "" | |
822 | if hasattr(obj, "__xmlnamespaces__"): | |
823 | ns = getattr(obj, "__xmlnamespaces__") | |
824 | keys = ns.keys() | |
825 | keys.sort() | |
826 | for nameSpaceKey in keys: | |
827 | nameSpaceUrl = ns[nameSpaceKey] | |
828 | if nameSpaceUrl in nameSpaces.values(): | |
829 | for k, v in nameSpaces.iteritems(): | |
830 | if v == nameSpaceUrl: | |
831 | nameSpaceKey = k | |
832 | break | |
2eeaec19 | 833 | else: |
02b800ce RD |
834 | if nameSpaceKey == "": |
835 | defaultLongNS = nameSpaceUrl | |
836 | nameSpaces[DEFAULT_NAMESPACE_KEY] = nameSpaceUrl | |
837 | newNS.nsMap[DEFAULT_NAMESPACE_KEY] = nameSpaceUrl | |
838 | nameSpaceAttrs += ' xmlns="%s" ' % (nameSpaceUrl) | |
839 | else: | |
840 | nameSpaces[nameSpaceKey] = nameSpaceUrl | |
841 | newNS.nsMap[nameSpaceKey] = nameSpaceUrl | |
842 | nameSpaceAttrs += ' xmlns:%s="%s" ' % (nameSpaceKey, nameSpaceUrl) | |
843 | nameSpaceAttrs = nameSpaceAttrs.rstrip() | |
844 | if len(self.nsstack) > 0: | |
845 | newNS.prefix = self.nsstack[-1].prefix | |
2eeaec19 | 846 | else: |
02b800ce | 847 | newNS.prefix = '' |
aca310e5 | 848 | if obj != None and hasattr(obj, "__xmldefaultnamespace__"): |
02b800ce RD |
849 | longPrefixNS = getattr(obj, "__xmldefaultnamespace__") |
850 | if longPrefixNS == defaultLongNS: | |
851 | newNS.prefix = '' | |
852 | else: | |
853 | try: | |
854 | for k, v in nameSpaces.iteritems(): | |
855 | if v == longPrefixNS: | |
856 | newNS.prefix = k + ':' | |
857 | break; | |
02b800ce RD |
858 | except: |
859 | if (longPrefixNS in asDict(self.knownNamespaces)): | |
860 | newNS.prefix = self.knownNamespaces[longPrefixNS] + ':' | |
861 | else: | |
862 | raise MarshallerException('Error marshalling __xmldefaultnamespace__ ("%s") not defined in namespace stack' % (longPrefixNS)) | |
aca310e5 | 863 | if obj != None and hasattr(obj, "targetNamespace"): |
02b800ce RD |
864 | newNS.targetNS = obj.targetNamespace |
865 | elif len(self.nsstack) > 0: | |
866 | newNS.targetNS = self.nsstack[-1].targetNS | |
867 | newNS.nameSpaces = nameSpaces | |
868 | self.nsstack.append(newNS) | |
869 | return nameSpaceAttrs | |
870 | ||
871 | def contractQName(self, value, obj, attr): | |
872 | value = langToXsdType(value) | |
873 | i = value.rfind(':') | |
874 | if i >= 0: | |
875 | longNS = value[:i] | |
2eeaec19 | 876 | else: |
02b800ce RD |
877 | # the value doesn't have a namespace and we couldn't map it to an XSD type...what to do? |
878 | # (a) just write it, as is, and hope it's in the default namespace (for now) | |
879 | # (b) throw an exception so we can track down the bad code (later) | |
880 | return value | |
881 | if (longNS in self.nsstack[-1].nameSpaces.values()): | |
882 | for kShort, vLong in self.nsstack[-1].nameSpaces.iteritems(): | |
883 | if vLong == longNS: | |
884 | shortNS = kShort | |
885 | break | |
886 | else: | |
887 | shortNS = longNS # if we can't find the long->short mappping, just use longNS | |
888 | if shortNS == DEFAULT_NAMESPACE_KEY: | |
889 | value = value[i+1:] | |
1f780e48 | 890 | else: |
02b800ce RD |
891 | value = shortNS + ':' + value[i+1:] |
892 | return value | |
893 | ||
894 | def _genObjTypeStr(self, typeString): | |
895 | if self.marshalType: | |
896 | return ' objtype="%s"' % typeString | |
897 | return "" | |
bbf7159c | 898 | |
02b800ce RD |
899 | def _marshal(self, obj, elementName=None, nameSpacePrefix="", indent=0): |
900 | if (obj != None): | |
aca310e5 | 901 | aglogging.debug(xmlMarshallerLogger, "--> _marshal: elementName=%s%s, type=%s, obj=%s, indent=%d", nameSpacePrefix, elementName, type(obj), str(obj), indent) |
02b800ce | 902 | else: |
aca310e5 RD |
903 | aglogging.debug(xmlMarshallerLogger, "--> _marshal: elementName=%s%s, obj is None, indent=%d", nameSpacePrefix, elementName, indent) |
904 | if ((obj != None) and (hasattr(obj, 'preMarshal'))): | |
905 | obj.preMarshal() | |
02b800ce RD |
906 | excludeAttrs = [] |
907 | excludeAttrs.extend(self.xmldeepexclude) | |
908 | if hasattr(obj, "__xmlexclude__"): | |
909 | excludeAttrs.extend(obj.__xmlexclude__) | |
910 | prettyPrint = self.prettyPrint | |
911 | knownTypes = self.knownTypes | |
912 | xmlString = None | |
913 | if self.prettyPrint or indent: | |
914 | prefix = " "*indent | |
915 | newline = "\n" | |
916 | increment = 2 | |
1f780e48 | 917 | else: |
02b800ce RD |
918 | prefix = "" |
919 | newline = "" | |
920 | increment = 0 | |
921 | ## Determine the XML element name. If it isn"t specified in the | |
aca310e5 RD |
922 | ## parameter list, look for it in the __xmlname__ attribute, |
923 | ## else use the default generic BASETYPE_ELEMENT_NAME. | |
02b800ce RD |
924 | nameSpaceAttrs = self.appendNSStack(obj) |
925 | nameSpacePrefix = self.getNSPrefix() | |
926 | if not elementName: | |
927 | if hasattr(obj, "__xmlname__"): | |
928 | elementName = nameSpacePrefix + obj.__xmlname__ | |
bbf7159c | 929 | else: |
02b800ce RD |
930 | elementName = nameSpacePrefix + BASETYPE_ELEMENT_NAME |
931 | else: | |
932 | elementName = nameSpacePrefix + elementName | |
02b800ce RD |
933 | |
934 | if (hasattr(obj, "__xmlsequencer__")) and (obj.__xmlsequencer__ != None): | |
935 | if (XMLSCHEMA_XSD_URL in self.nsstack[-1].nameSpaces.values()): | |
936 | for kShort, vLong in self.nsstack[-1].nameSpaces.iteritems(): | |
937 | if vLong == XMLSCHEMA_XSD_URL: | |
aca310e5 RD |
938 | if kShort != DEFAULT_NAMESPACE_KEY: |
939 | xsdPrefix = kShort + ':' | |
940 | else: | |
941 | xsdPrefix = '' | |
02b800ce RD |
942 | break |
943 | else: | |
944 | xsdPrefix = 'xs:' | |
945 | elementAdd = xsdPrefix + obj.__xmlsequencer__ | |
946 | else: | |
947 | elementAdd = None | |
948 | ||
02b800ce RD |
949 | members_to_skip = [] |
950 | ## Add more members_to_skip based on ones the user has selected | |
951 | ## via the __xmlexclude__ and __xmldeepexclude__ attributes. | |
952 | members_to_skip.extend(excludeAttrs) | |
953 | # Marshal the attributes that are selected to be XML attributes. | |
954 | objattrs = "" | |
955 | className = ag_className(obj) | |
956 | classNamePrefix = "_" + className | |
957 | if hasattr(obj, "__xmlattributes__"): | |
958 | xmlattributes = obj.__xmlattributes__ | |
959 | members_to_skip.extend(xmlattributes) | |
960 | for attr in xmlattributes: | |
02b800ce RD |
961 | internalAttrName = attr |
962 | ifDefPy() | |
963 | if (attr.startswith("__") and not attr.endswith("__")): | |
964 | internalAttrName = classNamePrefix + attr | |
965 | endIfDef() | |
966 | # Fail silently if a python attribute is specified to be | |
967 | # an XML attribute but is missing. | |
02b800ce RD |
968 | attrNameSpacePrefix = "" |
969 | if hasattr(obj, "__xmlattrnamespaces__"): | |
970 | for nameSpaceKey, nameSpaceAttributes in getattr(obj, "__xmlattrnamespaces__").iteritems(): | |
971 | if nameSpaceKey == nameSpacePrefix[:-1]: # Don't need to specify attribute namespace if it is the same as its element | |
972 | continue | |
973 | if attr in nameSpaceAttributes: | |
974 | attrNameSpacePrefix = nameSpaceKey + ":" | |
975 | break | |
976 | attrs = obj.__dict__ | |
977 | value = attrs.get(internalAttrName) | |
978 | if (hasattr(obj, "__xmlrename__") and attr in asDict(obj.__xmlrename__)): | |
979 | attr = obj.__xmlrename__[attr] | |
980 | xsdElement = None | |
981 | complexType = getComplexType(obj) | |
982 | if (complexType != None): | |
983 | xsdElement = complexType.findElement(attr) | |
984 | if (xsdElement != None): | |
985 | default = xsdElement.default | |
986 | if (default != None): | |
987 | if ((default == value) or (default == _getXmlValue(value))): | |
988 | continue | |
1f780e48 | 989 | else: |
02b800ce RD |
990 | if (value == None): |
991 | continue | |
992 | elif xsdElement.type == TYPE_QNAME: | |
993 | value = self.contractQName(value, obj, attr) | |
994 | elif value == None: | |
995 | continue | |
996 | ||
997 | # ToDO remove maxOccurs hack after bug 177 is fixed | |
998 | if attr == "maxOccurs" and value == -1: | |
999 | value = "unbounded" | |
1000 | ||
1001 | if isinstance(value, bool): | |
1002 | if value == True: | |
1003 | value = "true" | |
1004 | else: | |
1005 | value = "false" | |
1006 | else: | |
1007 | value = objutils.toDiffableRepr(value) | |
1008 | ||
aca310e5 | 1009 | objattrs += ' %s%s="%s"' % (attrNameSpacePrefix, attr, utillang.escape(value)) |
02b800ce RD |
1010 | if (obj == None): |
1011 | xmlString = [""] | |
1012 | elif isinstance(obj, bool): | |
1013 | objTypeStr = self._genObjTypeStr("bool") | |
1014 | xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, obj, elementName, newline)] | |
1015 | elif isinstance(obj, int): | |
1016 | objTypeStr = self._genObjTypeStr("int") | |
1017 | xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)] | |
1018 | elif isinstance(obj, long): | |
1019 | objTypeStr = self._genObjTypeStr("long") | |
1020 | xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)] | |
1021 | elif isinstance(obj, float): | |
1022 | objTypeStr = self._genObjTypeStr("float") | |
1023 | xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)] | |
1024 | elif isinstance(obj, unicode): # have to check before basestring - unicode is instance of base string | |
aca310e5 | 1025 | xmlString = ['%s<%s>%s</%s>%s' % (prefix, elementName, utillang.escape(obj.encode()), elementName, newline)] |
02b800ce | 1026 | elif isinstance(obj, basestring): |
aca310e5 RD |
1027 | xmlString = ['%s<%s>%s</%s>%s' % (prefix, elementName, utillang.escape(obj), elementName, newline)] |
1028 | elif isinstance(obj, datetime.datetime): | |
1029 | objTypeStr = self._genObjTypeStr("datetime") | |
1030 | xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)] | |
1031 | elif isinstance(obj, datetime.date): | |
1032 | objTypeStr = self._genObjTypeStr("date") | |
1033 | xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)] | |
1034 | elif isinstance(obj, datetime.time): | |
1035 | objTypeStr = self._genObjTypeStr("time") | |
1036 | xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)] | |
02b800ce RD |
1037 | elif isinstance(obj, list): |
1038 | if len(obj) < 1: | |
1039 | xmlString = "" | |
1040 | else: | |
1041 | objTypeStr = self._genObjTypeStr("list") | |
1042 | xmlString = ['%s<%s%s>%s' % (prefix, elementName, objTypeStr, newline)] | |
1043 | for item in obj: | |
1044 | xmlString.extend(self._marshal(item, indent=indent+increment)) | |
1045 | xmlString.append("%s</%s>%s" % (prefix, elementName, newline)) | |
1046 | elif isinstance(obj, tuple): | |
1047 | if len(obj) < 1: | |
1048 | xmlString = "" | |
1f780e48 | 1049 | else: |
02b800ce RD |
1050 | objTypeStr = self._genObjTypeStr("list") |
1051 | xmlString = ['%s<%s%s mutable="false">%s' % (prefix, elementName, objTypeStr, newline)] | |
1052 | for item in obj: | |
1053 | xmlString.extend(self._marshal(item, indent=indent+increment)) | |
2eeaec19 | 1054 | xmlString.append("%s</%s>%s" % (prefix, elementName, newline)) |
02b800ce RD |
1055 | elif isinstance(obj, dict): |
1056 | objTypeStr = self._genObjTypeStr("dict") | |
1057 | xmlString = ['%s<%s%s>%s' % (prefix, elementName, objTypeStr, newline)] | |
1058 | subprefix = prefix + " "*increment | |
1059 | subindent = indent + 2*increment | |
1060 | keys = obj.keys() | |
1061 | keys.sort() | |
1062 | for key in keys: | |
1063 | xmlString.append("%s<%s>%s" % (subprefix, DICT_ITEM_NAME, newline)) | |
1064 | xmlString.extend(self._marshal(key, elementName=DICT_ITEM_KEY_NAME, indent=subindent)) | |
1065 | xmlString.extend(self._marshal(obj[key], elementName=DICT_ITEM_VALUE_NAME, indent=subindent)) | |
1066 | xmlString.append("%s</%s>%s" % (subprefix, DICT_ITEM_NAME, newline)) | |
1067 | xmlString.append("%s</%s>%s" % (prefix, elementName, newline)) | |
1068 | elif hasattr(obj, "__xmlcontent__"): | |
1069 | contentValue = getattr(obj, obj.__xmlcontent__) | |
1070 | if contentValue == None: | |
aca310e5 | 1071 | xmlString = ["%s<%s%s%s/>%s" % (prefix, elementName, nameSpaceAttrs, objattrs, newline)] |
02b800ce | 1072 | else: |
aca310e5 RD |
1073 | contentValue = utillang.escape(contentValue) |
1074 | xmlString = ["%s<%s%s%s>%s</%s>%s" % (prefix, elementName, nameSpaceAttrs, objattrs, contentValue, elementName, newline)] | |
1f780e48 | 1075 | else: |
02b800ce | 1076 | # Only add the objtype if the element tag is unknown to us. |
aca310e5 RD |
1077 | if (isinstance(obj, GenericXMLObject)): |
1078 | objTypeStr = "" | |
1079 | elif (self.isKnownType(elementName) == True): | |
02b800ce RD |
1080 | objTypeStr = "" |
1081 | else: | |
1082 | objTypeStr = self._genObjTypeStr("%s.%s" % (obj.__class__.__module__, className)) | |
1083 | xmlString = ['%s<%s%s%s%s' % (prefix, elementName, nameSpaceAttrs, objattrs, objTypeStr)] | |
1084 | # get the member, value pairs for the object, filtering out the types we don"t support | |
1085 | if (elementAdd != None): | |
1086 | prefix += increment*" " | |
1087 | indent += increment | |
1088 | xmlMemberString = [] | |
1089 | if hasattr(obj, "__xmlbody__"): | |
1090 | xmlbody = getattr(obj, obj.__xmlbody__) | |
1091 | if xmlbody != None: | |
aca310e5 | 1092 | xmlMemberString.append(utillang.escape(xmlbody)) |
02b800ce RD |
1093 | else: |
1094 | if hasattr(obj, "__xmlattrgroups__"): | |
1095 | attrGroups = obj.__xmlattrgroups__.copy() | |
1096 | if (not isinstance(attrGroups, dict)): | |
1097 | raise "__xmlattrgroups__ is not a dict, but must be" | |
1098 | for n in attrGroups.iterkeys(): | |
1099 | members_to_skip.extend(attrGroups[n]) | |
1100 | else: | |
1101 | attrGroups = {} | |
1102 | # add the list of all attributes to attrGroups | |
1103 | eList = obj.__dict__.keys() | |
1104 | eList.sort() | |
1105 | attrGroups["__nogroup__"] = eList | |
1106 | ||
1107 | for eName, eList in attrGroups.iteritems(): | |
1108 | if (eName != "__nogroup__"): | |
1109 | prefix += increment*" " | |
1110 | indent += increment | |
1111 | objTypeStr = self._genObjTypeStr("None") | |
1112 | xmlMemberString.append('%s<%s%s>%s' % (prefix, eName, objTypeStr, newline)) | |
1113 | for name in eList: | |
1114 | value = obj.__dict__[name] | |
1115 | if eName == "__nogroup__" and name in members_to_skip: continue | |
1116 | if name.startswith("__") and name.endswith("__"): continue | |
1117 | if (hasattr(obj, "__xmlcdatacontent__") and (obj.__xmlcdatacontent__ == name)): | |
1118 | continue | |
1119 | subElementNameSpacePrefix = nameSpacePrefix | |
1120 | if hasattr(obj, "__xmlattrnamespaces__"): | |
1121 | for nameSpaceKey, nameSpaceValues in getattr(obj, "__xmlattrnamespaces__").iteritems(): | |
1122 | if name in nameSpaceValues: | |
1123 | subElementNameSpacePrefix = nameSpaceKey + ":" | |
1124 | break | |
1125 | # handle sequences listed in __xmlflattensequence__ | |
1126 | # specially: instead of listing the contained items inside | |
1127 | # of a separate list, as God intended, list them inside | |
1128 | # the object containing the sequence. | |
1129 | if (hasattr(obj, "__xmlflattensequence__") and (value != None) and (name in asDict(obj.__xmlflattensequence__))): | |
1130 | xmlnametuple = obj.__xmlflattensequence__[name] | |
1131 | if (xmlnametuple == None): | |
1132 | xmlnametuple = [name] | |
1133 | elif (not isinstance(xmlnametuple, (tuple,list))): | |
1134 | xmlnametuple = [str(xmlnametuple)] | |
1135 | xmlname = None | |
1136 | if (len(xmlnametuple) == 1): | |
1137 | xmlname = xmlnametuple[0] | |
02b800ce RD |
1138 | if not isinstance(value, (list, tuple)): |
1139 | value = [value] | |
1140 | for seqitem in value: | |
02b800ce RD |
1141 | xmlMemberString.extend(self._marshal(seqitem, xmlname, subElementNameSpacePrefix, indent=indent+increment)) |
1142 | else: | |
1143 | if (hasattr(obj, "__xmlrename__") and name in asDict(obj.__xmlrename__)): | |
1144 | xmlname = obj.__xmlrename__[name] | |
1145 | else: | |
1146 | xmlname = name | |
aca310e5 RD |
1147 | if (value != None): |
1148 | xmlMemberString.extend(self._marshal(value, xmlname, subElementNameSpacePrefix, indent=indent+increment)) | |
02b800ce RD |
1149 | if (eName != "__nogroup__"): |
1150 | xmlMemberString.append("%s</%s>%s" % (prefix, eName, newline)) | |
1151 | prefix = prefix[:-increment] | |
1152 | indent -= increment | |
1153 | ||
1154 | # if we have nested elements, add them here, otherwise close the element tag immediately. | |
1155 | newList = [] | |
1156 | for s in xmlMemberString: | |
1157 | if (len(s) > 0): newList.append(s) | |
1158 | xmlMemberString = newList | |
1159 | if len(xmlMemberString) > 0: | |
1160 | xmlString.append(">") | |
1161 | if hasattr(obj, "__xmlbody__"): | |
1162 | xmlString.extend(xmlMemberString) | |
1163 | xmlString.append("</%s>%s" % (elementName, newline)) | |
1164 | else: | |
1165 | xmlString.append(newline) | |
1166 | if (elementAdd != None): | |
1167 | xmlString.append("%s<%s>%s" % (prefix, elementAdd, newline)) | |
1168 | xmlString.extend(xmlMemberString) | |
1169 | if (elementAdd != None): | |
1170 | xmlString.append("%s</%s>%s" % (prefix, elementAdd, newline)) | |
1171 | prefix = prefix[:-increment] | |
1172 | indent -= increment | |
1173 | xmlString.append("%s</%s>%s" % (prefix, elementName, newline)) | |
1174 | else: | |
1175 | if hasattr(obj, "__xmlcdatacontent__"): | |
1176 | cdataAttr = obj.__xmlcdatacontent__ | |
1177 | cdataContent = obj.__dict__[cdataAttr] | |
1178 | xmlString.append("><![CDATA[%s]]></%s>%s" % (cdataContent, elementName, newline)) | |
1179 | else: | |
1180 | xmlString.append("/>%s" % newline) | |
aca310e5 RD |
1181 | if aglogging.isEnabledForDebug(xmlMarshallerLogger): |
1182 | aglogging.debug(xmlMarshallerLogger, "<-- _marshal: %s", objutils.toDiffableString(xmlString)) | |
02b800ce RD |
1183 | #print "<-- _marshal: %s" % str(xmlString) |
1184 | self.popNSStack() | |
1185 | return xmlString | |
2eeaec19 RD |
1186 | |
1187 | # A simple test, to be executed when the xmlmarshaller is run standalone | |
1188 | class MarshallerPerson: | |
1189 | __xmlname__ = "person" | |
1190 | __xmlexclude__ = ["fabulousness",] | |
1191 | __xmlattributes__ = ("nonSmoker",) | |
1192 | __xmlrename__ = {"_phoneNumber": "telephone"} | |
1193 | __xmlflattensequence__ = {"favoriteWords": ("vocabulary",)} | |
1194 | __xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]} | |
1195 | ||
1196 | def setPerson(self): | |
1197 | self.firstName = "Albert" | |
1198 | self.lastName = "Camus" | |
1199 | self.addressLine1 = "23 Absurd St." | |
1200 | self.city = "Ennui" | |
1201 | self.state = "MO" | |
1202 | self.zip = "54321" | |
1203 | self._phoneNumber = "808-303-2323" | |
1204 | self.favoriteWords = ["angst", "ennui", "existence"] | |
1205 | self.phobias = ["war", "tuberculosis", "cars"] | |
1206 | self.weight = 150 | |
1207 | self.fabulousness = "tres tres" | |
1208 | self.nonSmoker = False | |
1209 | ||
1210 | if isMain(__name__): | |
1211 | p1 = MarshallerPerson() | |
1212 | p1.setPerson() | |
1213 | xmlP1 = marshal(p1, prettyPrint=True, encoding="utf-8") | |
1214 | print "\n########################" | |
1215 | print "# testPerson test case #" | |
1216 | print "########################" | |
1217 | print xmlP1 | |
1218 | p2 = unmarshal(xmlP1) | |
1219 | xmlP2 = marshal(p2, prettyPrint=True, encoding="utf-8") | |
1220 | if xmlP1 == xmlP2: | |
1221 | print "Success: repeated marshalling yields identical results" | |
1222 | else: | |
1223 | print "Failure: repeated marshalling yields different results" | |
1224 | print xmlP2 |