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