]> git.saurik.com Git - wxWidgets.git/blob - wxPython/samples/ide/activegrid/util/xmlmarshaller.py
Merged modifications from the 2.6 branch
[wxWidgets.git] / wxPython / samples / ide / activegrid / util / xmlmarshaller.py
1 #----------------------------------------------------------------------------
2 # Name: xmlmarshaller.py
3 # Purpose:
4 #
5 # Authors: John Spurling, Joel Hare, Alan Mullendore
6 #
7 # Created: 7/28/04
8 # CVS-ID: $Id$
9 # Copyright: (c) 2004-2005 ActiveGrid, Inc.
10 # License: wxWindows License
11 #----------------------------------------------------------------------------
12 import sys
13 from types import *
14 from activegrid.util.lang import *
15 import logging
16 ifDefPy()
17 import xml.sax
18 import xml.sax.handler
19 endIfDef()
20 import xml.sax.saxutils as saxutils
21 import activegrid.util.objutils as objutils
22 import activegrid.util.aglogging as aglogging
23
24 MODULE_PATH = "__main__"
25
26 ## ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed
27
28 """
29 Special attributes that we recognize:
30
31 name: __xmlname__
32 type: string
33 description: the name of the xml element for the marshalled object
34
35 name: __xmlattributes__
36 type: tuple or list
37 description: the name(s) of the Lang string attribute(s) to be
38 marshalled as xml attributes instead of nested xml elements. currently
39 these can only be strings since there"s not a way to get the type
40 information back when unmarshalling.
41
42 name: __xmlexclude__
43 type: tuple or list
44 description: the name(s) of the lang attribute(s) to skip when
45 marshalling.
46
47 name: __xmlrename__
48 type: dict
49 description: describes an alternate Lang <-> XML name mapping.
50 Normally the name mapping is the identity function. __xmlrename__
51 overrides that. The keys are the Lang names, the values are their
52 associated XML names.
53
54 name: __xmlflattensequence__
55 type: dict, tuple, or list
56 description: the name(s) of the Lang sequence attribute(s) whose
57 items are to be marshalled as a series of xml elements (with an
58 optional keyword argument that specifies the element name to use) as
59 opposed to containing them in a separate sequence element, e.g.:
60
61 myseq = (1, 2)
62 <!-- normal way of marshalling -->
63 <myseq>
64 <item objtype="int">1</item>
65 <item objtype="int">2</item>
66 </myseq>
67 <!-- with __xmlflattensequence__ set to {"myseq": "squish"} -->
68 <squish objtype="int">1</squish>
69 <squish objtype="int">2</squish>
70
71 name: __xmlnamespaces__
72 type: dict
73 description: a dict of the namespaces that the object uses. Each item
74 in the dict should consist of a prefix,url combination where the key is
75 the prefix and url is the value, e.g.:
76
77 __xmlnamespaces__ = { "xsd":"http://www.w3c.org/foo.xsd" }
78
79 name: __xmldefaultnamespace__
80 type: String
81 description: the prefix of a namespace defined in __xmlnamespaces__ that
82 should be used as the default namespace for the object.
83
84 name: __xmlattrnamespaces__
85 type: dict
86 description: a dict assigning the Lang object"s attributes to the namespaces
87 defined in __xmlnamespaces__. Each item in the dict should consist of a
88 prefix,attributeList combination where the key is the prefix and the value is
89 a list of the Lang attribute names. e.g.:
90
91 __xmlattrnamespaces__ = { "ag":["firstName", "lastName", "addressLine1", "city"] }
92
93 name: __xmlattrgroups__
94 type: dict
95 description: a dict specifying groups of attributes to be wrapped in an enclosing tag.
96 The key is the name of the enclosing tag; the value is a list of attributes to include
97 within it. e.g.
98
99 __xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]}
100
101 name: __xmlcdatacontent__
102 type: string
103 description: value is the name of a string attribute that should be assigned CDATA content from the
104 source document and that should be marshalled as CDATA.
105
106 __xmlcdatacontent__ = "messyContent"
107
108 """
109
110 global xmlMarshallerLogger
111 xmlMarshallerLogger = logging.getLogger("activegrid.util.xmlmarshaller.marshal")
112 xmlMarshallerLogger.setLevel(aglogging.LEVEL_WARN)
113 # INFO : low-level info
114 # DEBUG : debugging info
115
116 ################################################################################
117 #
118 # module exceptions
119 #
120 ################################################################################
121
122 class Error(Exception):
123 """Base class for errors in this module."""
124 pass
125
126 class 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
135 class XMLAttributeIsNotStringType(Error):
136 """Exception raised when an object"s attribute is specified to be
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
145 instead of a nested element, but the object"s type is %s, not
146 string.""" % (self.attrname, self.typename)
147
148 class MarshallerException(Exception):
149 pass
150
151 class UnmarshallerException(Exception):
152 pass
153
154 ################################################################################
155 #
156 # constants and such
157 #
158 ################################################################################
159
160 XMLNS = "xmlns"
161 XMLNS_PREFIX = XMLNS + ":"
162 XMLNS_PREFIX_LENGTH = len(XMLNS_PREFIX)
163 DEFAULT_NAMESPACE_KEY = "__DEFAULTNS__"
164 TYPE_QNAME = "QName"
165 XMLSCHEMA_XSD_URL = "http://www.w3.org/2001/XMLSchema"
166 AG_URL = "http://www.activegrid.com/ag.xsd"
167
168 BASETYPE_ELEMENT_NAME = "item"
169 DICT_ITEM_NAME = "qqDictItem"
170 DICT_ITEM_KEY_NAME = "key"
171 DICT_ITEM_VALUE_NAME = "value"
172
173 # This list doesn"t seem to be used.
174 # Internal documentation or useless? You make the call!
175 ##MEMBERS_TO_SKIP = ("__module__", "__doc__", "__xmlname__", "__xmlattributes__",
176 ## "__xmlexclude__", "__xmlflattensequence__", "__xmlnamespaces__",
177 ## "__xmldefaultnamespace__", "__xmlattrnamespaces__",
178 ## "__xmlattrgroups__")
179
180 ################################################################################
181 #
182 # classes and functions
183 #
184 ################################################################################
185
186 def 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
195 def getComplexType(obj):
196 if (hasattr(obj, "__xsdcomplextype__")):
197 return obj.__xsdcomplextype__
198 return None
199
200 def _objectfactory(objname, objargs=None, objclass=None):
201 "dynamically create an object based on the objname and return it."
202 ## print "[objectfactory] objname [%s]" % (objname)
203 if not isinstance(objargs, list):
204 objargs = [objargs]
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)
213 else:
214 return objclass()
215 return objutils.newInstance(objname, objargs)
216
217 class Element:
218 def __init__(self, name, attrs=None, xsname=None):
219 self.name = name
220 self.attrs = attrs
221 self.content = ""
222 self.children = []
223 self.objclass = None
224 self.xsname = xsname
225
226 def getobjtype(self):
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
235 class 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
303
304 class XMLObjectFactory(xml.sax.ContentHandler):
305 def __init__(self, knownTypes=None, knownNamespaces=None):
306 self.rootelement = None
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
316 self.elementstack = []
317 self.nsstack = []
318 self.collectContent = None
319 xml.sax.handler.ContentHandler.__init__(self)
320
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()
328 else:
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
340
341 ## ContentHandler methods
342 def startElement(self, name, attrs):
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
378
379 def characters(self, content):
380 ## print '[characters] "%s" (%s)' % (content, type(content))
381 if (content != None):
382 if self.collectContent != None:
383 self.collectContent.content += content
384 else:
385 self.elementstack[-1].content += content
386
387 def endElement(self, name):
388 ## print "[endElement] </%s>" % name
389 xsname = name
390 if name.find(":") > -1: # Strip namespace prefixes for now until actually looking them up in xsd
391 name = name[name.find(":") + 1:]
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
404 oldChildren = self.elementstack[-1].children
405 element, nse = self.popElementStack()
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]
411 objtype = element.getobjtype()
412 ## print "element objtype is: ", objtype
413 if (objtype == "None"):
414 ## print "[endElement] %s: skipping a (objtype==None) end tag" % name
415 return
416 constructorarglist = []
417 if (len(element.content) > 0):
418 strippedElementContent = element.content.strip()
419 if (len(strippedElementContent) > 0):
420 constructorarglist.append(element.content)
421 obj = _objectfactory(objtype, constructorarglist, element.objclass)
422 complexType = getComplexType(obj)
423 if (obj != None):
424 if (hasattr(obj, "__xmlname__") and getattr(obj, "__xmlname__") == "sequence"):
425 self.elementstack[-1].children = oldChildren
426 return
427 if (len(element.attrs) > 0) and not isinstance(obj, list):
428 ## print "[endElement] %s: element has attrs and the obj is not a list" % name
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 = ""
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
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):
447 xsdElement = complexType.findElement(attrname)
448 if (xsdElement != None):
449 type = xsdElement.type
450 ## print 'Unmarshalling element "%s", attribute "%s" with type "%s"' % (name, xsdElement.name, type)
451 if (type != None):
452 if (type == TYPE_QNAME):
453 attr = nse.expandQName(name, attrname, attr)
454 type = xsdToLangType(type)
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)
459 try:
460 setattrignorecase(obj, _toAttrName(obj, attrname), attr)
461 except AttributeError:
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)
464 ## obj.__dict__[_toAttrName(obj, attrname)] = attr
465 # stuff any child attributes meant to be in a sequence via the __xmlflattensequence__
466 flattenDict = {}
467 if hasattr(obj, "__xmlflattensequence__"):
468 flatten = obj.__xmlflattensequence__
469 if (isinstance(flatten, dict)):
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:
477 ## print "[endElement]: adding flattenDict[%s] = %s" % (xmlname, sequencename)
478 flattenDict[xmlname] = sequencename
479 else:
480 raise Exception("Invalid type for __xmlflattensequence___ : it must be a dict")
481
482 # reattach an object"s attributes to it
483 for childname, child in element.children:
484 ## print "[endElement] childname is: ", childname, "; child is: ", child
485 if (childname in flattenDict):
486 sequencename = _toAttrName(obj, flattenDict[childname])
487 if (not hasattr(obj, sequencename)):
488 obj.__dict__[sequencename] = []
489 sequencevalue = getattr(obj, sequencename)
490 if (sequencevalue == None):
491 obj.__dict__[sequencename] = []
492 sequencevalue = getattr(obj, sequencename)
493 sequencevalue.append(child)
494 elif (objtype == "list"):
495 obj.append(child)
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
501 else:
502 ## print "childname = %s, obj = %s, child = %s" % (childname, repr(obj), repr(child))
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
508
509 if (complexType != None):
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)):
514 langType = xsdToLangType(element.type)
515 defaultValue = _objectfactory(langType, element.default)
516 obj.__dict__[elementName] = defaultValue
517
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
531 def getRootObject(self):
532 return self.rootelement
533
534 def _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
544 __typeMappingXsdToLang = {
545 "string": "str",
546 "char": "str",
547 "varchar": "str",
548 "date": "str", # ToDO Need to work out how to create lang date types
549 "boolean": "bool",
550 "decimal": "float", # ToDO Does python have a better fixed point type?
551 "int": "int",
552 "integer":"int",
553 "long": "long",
554 "float": "float",
555 "bool": "bool",
556 "str": "str",
557 "unicode": "unicode",
558 "short": "int",
559 "duration": "str", # see above (date)
560 "datetime": "str", # see above (date)
561 "time": "str", # see above (date)
562 "double": "float",
563 "QName" : "str",
564 "blob" : "str", # ag:blob
565 "currency" : "str", # ag:currency
566 }
567
568 def xsdToLangType(xsdType):
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:]
573 langType = __typeMappingXsdToLang.get(xsdType)
574 if (langType == None):
575 raise Exception("Unknown xsd type %s" % xsdType)
576 return langType
577
578 def langToXsdType(langType):
579 if langType in asDict(__typeMappingXsdToLang):
580 return '%s:%s' % (XMLSCHEMA_XSD_URL, langType)
581 return langType
582
583 def _getXmlValue(langValue):
584 if (isinstance(langValue, bool)):
585 return str(langValue).lower()
586 elif (isinstance(langValue, unicode)):
587 return langValue.encode()
588 else:
589 return str(langValue)
590
591 def 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)
600 return objectfactory.getRootObject()
601
602 def 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))
608 if (isinstance(encoding, basestring)):
609 return '<?xml version="1.0" encoding="%s"?>\n%s' % (encoding, xmlstr.encode(encoding))
610 elif (encoding == None):
611 return xmlstr
612 else:
613 return '<?xml version="1.0" encoding="%s"?>\n%s' % (sys.getdefaultencoding(), xmlstr)
614
615 class XMLMarshalWorker(object):
616 def __init__(self, marshalType=True, prettyPrint=False, knownTypes=None, knownNamespaces=None):
617 if knownTypes == None:
618 self.knownTypes = {}
619 else:
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
683 else:
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
696 else:
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]
727 else:
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:]
741 else:
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 ""
749
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
766 else:
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__
778 else:
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
839 else:
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 = ""
891 else:
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))
896 xmlString.append("%s</%s>%s" % (prefix, elementName, newline))
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)]
917 else:
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
1030
1031 # A simple test, to be executed when the xmlmarshaller is run standalone
1032 class 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
1054 if 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