]> git.saurik.com Git - wxWidgets.git/blame - wxPython/samples/ide/activegrid/util/xmlmarshaller.py
DocView and ActiveGrid IDE updates from Morgan Hua:
[wxWidgets.git] / wxPython / samples / ide / activegrid / util / xmlmarshaller.py
CommitLineData
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 12import sys
1f780e48 13from types import *
02b800ce 14from activegrid.util.lang import *
2eeaec19 15import logging
02b800ce 16ifDefPy()
1f780e48
RD
17import xml.sax
18import xml.sax.handler
aca310e5
RD
19import xml.sax.saxutils
20import datetime
02b800ce 21endIfDef()
aca310e5 22import activegrid.util.utillang as utillang
02b800ce 23import activegrid.util.objutils as objutils
aca310e5 24import activegrid.util.sysutils as sysutils
2eeaec19 25import activegrid.util.aglogging as aglogging
6f1a3f9c
RD
26
27MODULE_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 33Special attributes that we recognize:
1f780e48
RD
34
35name: __xmlname__
36type: string
37description: the name of the xml element for the marshalled object
38
39name: __xmlattributes__
40type: tuple or list
2eeaec19 41description: the name(s) of the Lang string attribute(s) to be
1f780e48 42marshalled as xml attributes instead of nested xml elements. currently
2eeaec19 43these can only be strings since there"s not a way to get the type
1f780e48
RD
44information back when unmarshalling.
45
46name: __xmlexclude__
47type: tuple or list
2eeaec19 48description: the name(s) of the lang attribute(s) to skip when
1f780e48
RD
49marshalling.
50
51name: __xmlrename__
52type: dict
2eeaec19 53description: describes an alternate Lang <-> XML name mapping.
1f780e48 54Normally the name mapping is the identity function. __xmlrename__
2eeaec19 55overrides that. The keys are the Lang names, the values are their
1f780e48
RD
56associated XML names.
57
58name: __xmlflattensequence__
59type: dict, tuple, or list
2eeaec19 60description: the name(s) of the Lang sequence attribute(s) whose
1f780e48
RD
61items are to be marshalled as a series of xml elements (with an
62optional keyword argument that specifies the element name to use) as
63opposed to containing them in a separate sequence element, e.g.:
64
65myseq = (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
75name: __xmlnamespaces__
76type: dict
77description: a dict of the namespaces that the object uses. Each item
78in the dict should consist of a prefix,url combination where the key is
79the prefix and url is the value, e.g.:
80
81__xmlnamespaces__ = { "xsd":"http://www.w3c.org/foo.xsd" }
82
83name: __xmldefaultnamespace__
84type: String
85description: the prefix of a namespace defined in __xmlnamespaces__ that
86should be used as the default namespace for the object.
87
88name: __xmlattrnamespaces__
89type: dict
2eeaec19 90description: a dict assigning the Lang object"s attributes to the namespaces
1f780e48
RD
91defined in __xmlnamespaces__. Each item in the dict should consist of a
92prefix,attributeList combination where the key is the prefix and the value is
2eeaec19 93a list of the Lang attribute names. e.g.:
1f780e48
RD
94
95__xmlattrnamespaces__ = { "ag":["firstName", "lastName", "addressLine1", "city"] }
96
bbf7159c
RD
97name: __xmlattrgroups__
98type: dict
99description: a dict specifying groups of attributes to be wrapped in an enclosing tag.
100The key is the name of the enclosing tag; the value is a list of attributes to include
101within it. e.g.
102
103__xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]}
1f780e48 104
02b800ce
RD
105name: __xmlcdatacontent__
106type: string
107description: value is the name of a string attribute that should be assigned CDATA content from the
108source document and that should be marshalled as CDATA.
109
110__xmlcdatacontent__ = "messyContent"
111
1f780e48
RD
112"""
113
2eeaec19
RD
114global xmlMarshallerLogger
115xmlMarshallerLogger = 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
125class Error(Exception):
126 """Base class for errors in this module."""
127 pass
128
129class 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
138class 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 151class MarshallerException(Exception):
02b800ce
RD
152 pass
153
154class UnmarshallerException(Exception):
2eeaec19
RD
155 pass
156
1f780e48
RD
157################################################################################
158#
159# constants and such
160#
161################################################################################
162
2eeaec19
RD
163XMLNS = "xmlns"
164XMLNS_PREFIX = XMLNS + ":"
1f780e48 165XMLNS_PREFIX_LENGTH = len(XMLNS_PREFIX)
02b800ce
RD
166DEFAULT_NAMESPACE_KEY = "__DEFAULTNS__"
167TYPE_QNAME = "QName"
168XMLSCHEMA_XSD_URL = "http://www.w3.org/2001/XMLSchema"
169AG_URL = "http://www.activegrid.com/ag.xsd"
1f780e48 170
2eeaec19
RD
171BASETYPE_ELEMENT_NAME = "item"
172DICT_ITEM_NAME = "qqDictItem"
173DICT_ITEM_KEY_NAME = "key"
174DICT_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 189def 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
199def 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
206def _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
226class 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 289class 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
309class 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
395class 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
674def _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
684def 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 713def 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
723def langToXsdType(langType):
724 if langType in asDict(__typeMappingXsdToLang):
725 return '%s:%s' % (XMLSCHEMA_XSD_URL, langType)
726 return langType
1f780e48 727
2eeaec19
RD
728def _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
736def 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 750def 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
765class 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
1188class 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
1210if 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