+++ /dev/null
-#----------------------------------------------------------------------------
-# Name: xmlmarshaller.py
-# Purpose:
-#
-# Authors: John Spurling, Joel Hare, Jeff Norton, Alan Mullendore
-#
-# Created: 7/28/04
-# CVS-ID: $Id$
-# Copyright: (c) 2004-2005 ActiveGrid, Inc.
-# License: wxWindows License
-#----------------------------------------------------------------------------
-import sys
-from types import *
-from activegrid.util.lang import *
-import logging
-ifDefPy()
-import xml.sax
-import xml.sax.handler
-import xml.sax.saxutils
-import datetime
-endIfDef()
-import activegrid.util.utillang as utillang
-import activegrid.util.objutils as objutils
-import activegrid.util.sysutils as sysutils
-import activegrid.util.aglogging as aglogging
-
-MODULE_PATH = "__main__"
-
-## ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed
-##unboundedVal = 2147483647 # value used for maxOccurs == "unbounded"
-
-"""
-Special attributes that we recognize:
-
-name: __xmlname__
-type: string
-description: the name of the xml element for the marshalled object
-
-name: __xmlattributes__
-type: tuple or list
-description: the name(s) of the Lang string attribute(s) to be
-marshalled as xml attributes instead of nested xml elements. currently
-these can only be strings since there"s not a way to get the type
-information back when unmarshalling.
-
-name: __xmlexclude__
-type: tuple or list
-description: the name(s) of the lang attribute(s) to skip when
-marshalling.
-
-name: __xmlrename__
-type: dict
-description: describes an alternate Lang <-> XML name mapping.
-Normally the name mapping is the identity function. __xmlrename__
-overrides that. The keys are the Lang names, the values are their
-associated XML names.
-
-name: __xmlflattensequence__
-type: dict, tuple, or list
-description: the name(s) of the Lang sequence attribute(s) whose
-items are to be marshalled as a series of xml elements (with an
-optional keyword argument that specifies the element name to use) as
-opposed to containing them in a separate sequence element, e.g.:
-
-myseq = (1, 2)
-<!-- normal way of marshalling -->
-<myseq>
- <item objtype="int">1</item>
- <item objtype="int">2</item>
-</myseq>
-<!-- with __xmlflattensequence__ set to {"myseq": "squish"} -->
-<squish objtype="int">1</squish>
-<squish objtype="int">2</squish>
-
-name: __xmlnamespaces__
-type: dict
-description: a dict of the namespaces that the object uses. Each item
-in the dict should consist of a prefix,url combination where the key is
-the prefix and url is the value, e.g.:
-
-__xmlnamespaces__ = { "xsd":"http://www.w3c.org/foo.xsd" }
-
-name: __xmldefaultnamespace__
-type: String
-description: the prefix of a namespace defined in __xmlnamespaces__ that
-should be used as the default namespace for the object.
-
-name: __xmlattrnamespaces__
-type: dict
-description: a dict assigning the Lang object"s attributes to the namespaces
-defined in __xmlnamespaces__. Each item in the dict should consist of a
-prefix,attributeList combination where the key is the prefix and the value is
-a list of the Lang attribute names. e.g.:
-
-__xmlattrnamespaces__ = { "ag":["firstName", "lastName", "addressLine1", "city"] }
-
-name: __xmlattrgroups__
-type: dict
-description: a dict specifying groups of attributes to be wrapped in an enclosing tag.
-The key is the name of the enclosing tag; the value is a list of attributes to include
-within it. e.g.
-
-__xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]}
-
-name: __xmlcdatacontent__
-type: string
-description: value is the name of a string attribute that should be assigned CDATA content from the
-source document and that should be marshalled as CDATA.
-
-__xmlcdatacontent__ = "messyContent"
-
-"""
-
-global xmlMarshallerLogger
-xmlMarshallerLogger = logging.getLogger("activegrid.util.xmlmarshaller.marshal")
-# INFO : low-level info
-# DEBUG : debugging info
-
-################################################################################
-#
-# module exceptions
-#
-################################################################################
-
-class Error(Exception):
- """Base class for errors in this module."""
- pass
-
-class UnhandledTypeException(Error):
- """Exception raised when attempting to marshal an unsupported
- type.
- """
- def __init__(self, typename):
- self.typename = typename
- def __str__(self):
- return "%s is not supported for marshalling." % str(self.typename)
-
-class XMLAttributeIsNotStringType(Error):
- """Exception raised when an object"s attribute is specified to be
- marshalled as an XML attribute of the enclosing object instead of
- a nested element.
- """
- def __init__(self, attrname, typename):
- self.attrname = attrname
- self.typename = typename
- def __str__(self):
- return """%s was set to be marshalled as an XML attribute
- instead of a nested element, but the object"s type is %s, not
- string.""" % (self.attrname, self.typename)
-
-class MarshallerException(Exception):
- pass
-
-class UnmarshallerException(Exception):
- pass
-
-################################################################################
-#
-# constants and such
-#
-################################################################################
-
-XMLNS = "xmlns"
-XMLNS_PREFIX = XMLNS + ":"
-XMLNS_PREFIX_LENGTH = len(XMLNS_PREFIX)
-DEFAULT_NAMESPACE_KEY = "__DEFAULTNS__"
-TYPE_QNAME = "QName"
-XMLSCHEMA_XSD_URL = "http://www.w3.org/2001/XMLSchema"
-AG_URL = "http://www.activegrid.com/ag.xsd"
-
-BASETYPE_ELEMENT_NAME = "item"
-DICT_ITEM_NAME = "qqDictItem"
-DICT_ITEM_KEY_NAME = "key"
-DICT_ITEM_VALUE_NAME = "value"
-
-# This list doesn"t seem to be used.
-# Internal documentation or useless? You make the call!
-##MEMBERS_TO_SKIP = ("__module__", "__doc__", "__xmlname__", "__xmlattributes__",
-## "__xmlexclude__", "__xmlflattensequence__", "__xmlnamespaces__",
-## "__xmldefaultnamespace__", "__xmlattrnamespaces__",
-## "__xmlattrgroups__")
-
-################################################################################
-#
-# classes and functions
-#
-################################################################################
-
-def setattrignorecase(object, name, value):
-## print "[setattrignorecase] name = %s, value = %s" % (name, value)
- if (name not in object.__dict__):
- namelow = name.lower()
- for attr in object.__dict__:
- if attr.lower() == namelow:
- object.__dict__[attr] = value
- return
- object.__dict__[name] = value
-
-def getComplexType(obj):
- if (hasattr(obj, "_instancexsdcomplextype")):
- return obj._instancexsdcomplextype
- if (hasattr(obj, "__xsdcomplextype__")):
- return obj.__xsdcomplextype__
- return None
-
-def _objectfactory(objtype, objargs=None, objclass=None):
- "dynamically create an object based on the objtype and return it."
- if not isinstance(objargs, list):
- objargs = [objargs]
- if (objclass != None):
- obj = None
- if (len(objargs) > 0):
- if (hasattr(objclass, "__xmlcdatacontent__")):
- obj = objclass()
- contentAttr = obj.__xmlcdatacontent__
- obj.__dict__[contentAttr] = str(objargs[0])
- else:
- obj = objclass(*objargs)
- else:
- obj = objclass()
- if ((obj != None) and (hasattr(obj, 'postUnmarshal'))):
- obj.postUnmarshal()
- return obj
- return objutils.newInstance(objtype, objargs)
-
-class GenericXMLObject(object):
- def __init__(self, content=None):
- if content != None:
- self._content = content
- self.__xmlcontent__ = '_content'
-
- def __str__(self):
- return "GenericXMLObject(%s)" % objutils.toDiffableString(self.__dict__)
-
- def setXMLAttributes(self, xmlName, attrs=None, children=None, nsMap=None, defaultNS=None):
- if xmlName != None:
- i = xmlName.rfind(':')
- if i < 0:
- self.__xmlname__ = xmlName
- if defaultNS != None:
- self.__xmldefaultnamespace__ = str(defaultNS)
- else:
- self.__xmlname__ = xmlName[i+1:]
- prefix = xmlName[:i]
- if nsMap.has_key(prefix):
- self.__xmldefaultnamespace__ = str(nsMap[prefix])
- if attrs != None:
- for attrname, attr in attrs.items():
- attrname = str(attrname)
- if attrname == XMLNS or attrname.startswith(XMLNS_PREFIX):
- pass
- elif attrname == "objtype":
- pass
- else:
- if not hasattr(self, '__xmlattributes__'):
- self.__xmlattributes__ = []
- i = attrname.rfind(':')
- if i >= 0:
- prefix = attrname[:i]
- attrname = attrname[i+1:]
- if not hasattr(self, '__xmlattrnamespaces__'):
- self.__xmlattrnamespaces__ = {}
- if self.__xmlattrnamespaces__.has_key(prefix):
- alist = self.__xmlattrnamespaces__[prefix]
- else:
- alist = []
- alist.append(attrname)
- self.__xmlattrnamespaces__[prefix] = alist
- self.__xmlattributes__.append(attrname)
- if hasattr(self, '__xmlattributes__'):
- self.__xmlattributes__.sort()
- if children != None and len(children) > 0:
- childList = []
- flattenList = {}
- for childname, child in children:
- childstr = str(childname)
- if childstr in childList:
- if not flattenList.has_key(childstr):
- flattenList[childstr] = (childstr,)
- else:
- childList.append(childstr)
- if len(flattenList) > 0:
- self.__xmlflattensequence__ = flattenList
-
- def initialize(self, arg1=None):
- pass
-
-
-class Element:
- def __init__(self, name, attrs=None, xsname=None):
- self.name = name
- self.attrs = attrs
- self.content = ""
- self.children = []
- self.objclass = None
- self.xsname = xsname
- self.objtype = None
-
- def getobjtype(self):
-# objtype = self.attrs.get("objtype")
- objtype = self.objtype
- if (objtype == None):
- if (len(self.children) > 0):
- objtype = "dict"
- else:
- objtype = "str"
- return objtype
-
-class NsElement(object):
- def __init__(self):
- self.nsMap = {}
- self.targetNS = None
- self.defaultNS = None
- self.prefix = None
-
- def __str__(self):
- if self.prefix == None:
- strVal = 'prefix = None; '
- else:
- strVal = 'prefix = "%s"; ' % (self.prefix)
- if self.targetNS == None:
- strVal += 'targetNS = None; '
- else:
- strVal += 'targetNS = "%s"; ' % (self.targetNS)
- if self.defaultNS == None:
- strVal += 'defaultNS = None; '
- else:
- strVal += 'defaultNS = "%s"; ' % (self.defaultNS)
- if len(self.nsMap) == 0:
- strVal += 'nsMap = None; '
- else:
- strVal += 'nsMap = {'
- for ik, iv in self.nsMap.iteritems():
- strVal += '%s=%s; ' % (ik,iv)
- strVal += '}'
- return strVal
-
- def setKnownTypes(self, masterKnownTypes, masterKnownNamespaces, parentNSE):
- # if we're a nested element, extend our parent element's mapping
- if parentNSE != None:
- self.knownTypes = parentNSE.knownTypes.copy()
- # but if we have a different default namespace, replace the parent's default mappings
- if (self.defaultNS != None) and (parentNSE.defaultNS != self.defaultNS):
- newKT = self.knownTypes.copy()
- for tag in newKT:
- if tag.find(':') < 0:
- del self.knownTypes[tag]
- newMap = parentNSE.nsMap.copy()
- if self.nsMap != {}:
- for k, v in self.nsMap.iteritems():
- newMap[k] = v
- self.nsMap = newMap
- else:
- self.knownTypes = {}
- reversedKNS = {}
- # TODO: instead of starting with the knownNamespaces, start with the "xmlms" mappings
- # for this element. Then we'd only process the namespaces and tags we need to.
- # But for now, this works.
- for long, short in masterKnownNamespaces.iteritems():
- reversedKNS[short] = long
- mapLongs = self.nsMap.values()
- for tag, mapClass in masterKnownTypes.iteritems():
- i = tag.rfind(':')
- if i >= 0: # e.g. "wsdl:description"
- knownTagShort = tag[:i] # "wsdl"
- knownTagName = tag[i+1:] # "description"
- knownTagLong = reversedKNS[knownTagShort] # e.g. "http://schemas.xmlsoap.org/wsdl"
- if (knownTagLong in mapLongs):
- for mShort, mLong in self.nsMap.iteritems():
- if mLong == knownTagLong:
- actualShort = mShort # e.g. "ws"
- actualTag = '%s:%s' % (actualShort, knownTagName)
- self.knownTypes[actualTag] = mapClass
- break
- if self.defaultNS == knownTagLong:
- self.knownTypes[knownTagName] = mapClass
- else: # e.g. "ItemSearchRequest"
- self.knownTypes[tag] = mapClass
-
- def expandQName(self, eName, attrName, attrValue):
- bigValue = attrValue
- i = attrValue.rfind(':')
- if (i < 0):
- if self.defaultNS != None:
- bigValue = '%s:%s' % (self.defaultNS, attrValue)
- else:
- attrNS = attrValue[:i]
- attrNCName = attrValue[i+1:]
- for shortNs, longNs in self.nsMap.iteritems():
- if shortNs == attrNS:
- bigValue = '%s:%s' % (longNs, attrNCName)
- break
- return bigValue
-
-class XMLObjectFactory(xml.sax.ContentHandler):
- def __init__(self, knownTypes=None, knownNamespaces=None, xmlSource=None, createGenerics=False):
- self.rootelement = None
- if xmlSource == None:
- self.xmlSource = "unknown"
- else:
- self.xmlSource = xmlSource
- self.createGenerics = createGenerics
- self.skipper = False
- self.elementstack = []
- self.nsstack = []
- self.collectContent = None
- if (knownNamespaces == None):
- self.knownNamespaces = {}
- else:
- self.knownNamespaces = knownNamespaces
- self.reversedNamespaces = {}
- for longns, shortns in self.knownNamespaces.iteritems():
- self.reversedNamespaces[shortns] = longns
- self.knownTypes = {}
- if (knownTypes != None):
- for tag, cls in knownTypes.iteritems():
- i = tag.rfind(':')
- if i >= 0:
- shortns = tag[:i]
- tag = tag[i+1:]
- if not self.reversedNamespaces.has_key(shortns):
- errorString = 'Error unmarshalling XML document from source "%s": knownTypes specifies an unmapped short namespace "%s" for element "%s"' % (self.xmlSource, shortns, tag)
- raise UnmarshallerException(errorString)
- longns = self.reversedNamespaces[shortns]
- tag = '%s:%s' % (longns, tag)
- self.knownTypes[tag] = cls
- #printKnownTypes(self.knownTypes, 'Unmarshaller.XMLObjectFactory.__init__')
- xml.sax.handler.ContentHandler.__init__(self)
-
- def appendElementStack(self, newElement, newNS):
- self.elementstack.append(newElement)
- if (len(self.nsstack) > 0):
- oldNS = self.nsstack[-1]
- if newNS.defaultNS == None:
- newNS.defaultNS = oldNS.defaultNS
- if newNS.targetNS == None:
- newNS.targetNS = oldNS.targetNS
- if len(newNS.nsMap) == 0:
- newNS.nsMap = oldNS.nsMap
- elif len(oldNS.nsMap) > 0:
- map = oldNS.nsMap.copy()
- map.update(newNS.nsMap)
- newNS.nsMap = map
- self.nsstack.append(newNS)
- return newNS
-
- def popElementStack(self):
- element = self.elementstack.pop()
- nse = self.nsstack.pop()
- return element, nse
-
- ## ContentHandler methods
- def startElement(self, name, attrs):
-## print '[startElement] <%s>' % (name)
- if name == 'xs:annotation' or name == 'xsd:annotation': # should use namespace mapping here
- self.skipper = True
- self.appendElementStack(Element(name, attrs.copy()), NsElement())
- if self.skipper:
- return
- if self.collectContent != None:
- strVal = '<%s' % (name)
- for aKey, aVal in attrs.items():
- strVal += (' %s="%s"' % (aKey, aVal))
- strVal += '>'
- self.collectContent.content += strVal
- xsname = name
- i = name.rfind(':')
- if i >= 0:
- nsname = name[:i]
- name = name[i+1:]
- else:
- nsname = None
- element = Element(name, attrs.copy(), xsname=xsname)
- # if the element has namespace attributes, process them and add them to our stack
- nse = NsElement()
- objtype = None
- for k in attrs.getNames():
- if k.startswith('xmlns'):
- longNs = attrs[k]
- eLongNs = longNs + '/'
- if str(eLongNs) in asDict(self.knownNamespaces):
- longNs = eLongNs
- if k == 'xmlns':
- nse.defaultNS = longNs
- else:
- shortNs = k[6:]
- nse.nsMap[shortNs] = longNs
- elif k == 'targetNamespace':
- nse.targetNS = attrs.getValue(k)
- elif k == 'objtype':
- objtype = attrs.getValue(k)
- nse = self.appendElementStack(element, nse)
- if nsname != None:
- if nse.nsMap.has_key(nsname):
- longname = '%s:%s' % (nse.nsMap[nsname], name)
-## elif objtype == None:
-## 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())
-## raise UnmarshallerException(errorString)
- elif self.reversedNamespaces.has_key(nsname):
- longname = '%s:%s' % (self.reversedNamespaces[nsname], name)
- else:
- longname = xsname
- elif nse.defaultNS != None:
- longname = '%s:%s' % (nse.defaultNS, name)
- else:
- longname = name
- element.objtype = objtype
- element.objclass = self.knownTypes.get(longname)
- if element.objclass == None and len(self.knownNamespaces) == 0:
- # handles common case where tags are unqualified and knownTypes are too, but there's a defaultNS
- element.objclass = self.knownTypes.get(name)
- if (hasattr(element.objclass, "__xmlcontent__")):
- self.collectContent = element
-
- def characters(self, content):
-## print '[characters] "%s" (%s)' % (content, type(content))
- if (content != None):
- if self.collectContent != None:
- self.collectContent.content += content
- else:
- self.elementstack[-1].content += content
-
- def endElement(self, name):
-## print "[endElement] </%s>" % name
- xsname = name
- i = name.rfind(':')
- if i >= 0: # Strip namespace prefixes for now until actually looking them up in xsd
- name = name[i+1:]
- if self.skipper:
- if xsname == "xs:annotation" or xsname == "xsd:annotation": # here too
- self.skipper = False
- self.popElementStack()
- return
- if self.collectContent != None:
- if xsname != self.collectContent.xsname:
- self.collectContent.content += ('</%s>' % (xsname))
- self.popElementStack()
- return
- else:
- self.collectContent = None
- oldChildren = self.elementstack[-1].children
- element, nse = self.popElementStack()
- if ((len(self.elementstack) > 1) and (self.elementstack[-1].getobjtype() == "None")):
- parentElement = self.elementstack[-2]
- elif (len(self.elementstack) > 0):
- parentElement = self.elementstack[-1]
- objtype = element.getobjtype()
- if (objtype == "None"):
- return
- constructorarglist = []
- if (len(element.content) > 0):
- strippedElementContent = element.content.strip()
- if (len(strippedElementContent) > 0):
- constructorarglist.append(element.content)
- # If the element requires an object, but none is known, use the GenericXMLObject class
- if ((element.objclass == None) and (element.attrs.get("objtype") == None) and ((len(element.attrs) > 0) or (len(element.children) > 0))):
- if self.createGenerics:
- element.objclass = GenericXMLObject
- obj = _objectfactory(objtype, constructorarglist, element.objclass)
- if element.objclass == GenericXMLObject:
- obj.setXMLAttributes(str(xsname), element.attrs, element.children, nse.nsMap, nse.defaultNS)
- complexType = getComplexType(obj)
- if (obj != None):
- if (hasattr(obj, "__xmlname__") and getattr(obj, "__xmlname__") == "sequence"):
- self.elementstack[-1].children = oldChildren
- return
- if (len(element.attrs) > 0) and not isinstance(obj, list):
- for attrname, attr in element.attrs.items():
- if attrname == XMLNS or attrname.startswith(XMLNS_PREFIX):
- if attrname.startswith(XMLNS_PREFIX):
- ns = attrname[XMLNS_PREFIX_LENGTH:]
- else:
- ns = ""
- if complexType != None or element.objclass == GenericXMLObject:
- if not hasattr(obj, "__xmlnamespaces__"):
- obj.__xmlnamespaces__ = {ns:attr}
- elif ns not in obj.__xmlnamespaces__:
- if (hasattr(obj.__class__, "__xmlnamespaces__")
- and (obj.__xmlnamespaces__ is obj.__class__.__xmlnamespaces__)):
- obj.__xmlnamespaces__ = dict(obj.__xmlnamespaces__)
- obj.__xmlnamespaces__[ns] = attr
- elif not attrname == "objtype":
- if attrname.find(":") > -1: # Strip namespace prefixes for now until actually looking them up in xsd
- attrname = attrname[attrname.find(":") + 1:]
- if (complexType != None):
- xsdElement = complexType.findElement(attrname)
- if (xsdElement != None):
- type = xsdElement.type
- if (type != None):
- if (type == TYPE_QNAME):
- attr = nse.expandQName(name, attrname, attr)
- type = xsdToLangType(type)
- ### ToDO remove maxOccurs hack after bug 177 is fixed
- if attrname == "maxOccurs" and attr == "unbounded":
- attr = "-1"
- try:
- attr = _objectfactory(type, attr)
- except Exception, exceptData:
- 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))
- raise UnmarshallerException(errorString)
- try:
- setattrignorecase(obj, _toAttrName(obj, attrname), attr)
- except AttributeError:
- 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)
- raise UnmarshallerException(errorString)
-## obj.__dict__[_toAttrName(obj, attrname)] = attr
- # stuff any child attributes meant to be in a sequence via the __xmlflattensequence__
- flattenDict = {}
- if hasattr(obj, "__xmlflattensequence__"):
- flatten = obj.__xmlflattensequence__
- if (isinstance(flatten, dict)):
- for sequencename, xmlnametuple in flatten.items():
- if (xmlnametuple == None):
- flattenDict[sequencename] = sequencename
- elif (not isinstance(xmlnametuple, (tuple, list))):
- flattenDict[str(xmlnametuple)] = sequencename
- else:
- for xmlname in xmlnametuple:
- flattenDict[xmlname] = sequencename
- else:
- raise Exception("Invalid type for __xmlflattensequence___ : it must be a dict")
-
- # reattach an object"s attributes to it
- for childname, child in element.children:
- if (childname in flattenDict):
- sequencename = _toAttrName(obj, flattenDict[childname])
- if (not hasattr(obj, sequencename)):
- obj.__dict__[sequencename] = []
- sequencevalue = getattr(obj, sequencename)
- if (sequencevalue == None):
- obj.__dict__[sequencename] = []
- sequencevalue = getattr(obj, sequencename)
- sequencevalue.append(child)
- elif (objtype == "list"):
- obj.append(child)
- elif isinstance(obj, dict):
- if (childname == DICT_ITEM_NAME):
- obj[child[DICT_ITEM_KEY_NAME]] = child[DICT_ITEM_VALUE_NAME]
- else:
- obj[childname] = child
- else:
- # don't replace a good attribute value with a bad one
- childAttrName = _toAttrName(obj, childname)
- if (not hasattr(obj, childAttrName)) or (getattr(obj, childAttrName) == None) or (getattr(obj, childAttrName) == []) or (not isinstance(child, GenericXMLObject)):
- try:
- setattrignorecase(obj, childAttrName, child)
- except AttributeError:
- raise MarshallerException("Error unmarshalling child element \"%s\" of XML element \"%s\": object type not specified or known" % (childname, name))
-
- if (complexType != None):
- for element in complexType.elements:
- if element.default:
- elementName = _toAttrName(obj, element.name)
- if ((elementName not in obj.__dict__) or (obj.__dict__[elementName] == None)):
- langType = xsdToLangType(element.type)
- defaultValue = _objectfactory(langType, element.default)
- obj.__dict__[elementName] = defaultValue
-
- ifDefPy()
- if (isinstance(obj, list)):
- if ((element.attrs.has_key("mutable")) and (element.attrs.getValue("mutable") == "false")):
- obj = tuple(obj)
- endIfDef()
-
- if (len(self.elementstack) > 0):
-## print "[endElement] appending child with name: ", name, "; objtype: ", objtype
- parentElement.children.append((name, obj))
- else:
- self.rootelement = obj
-
- def getRootObject(self):
- return self.rootelement
-
-def _toAttrName(obj, name):
- if (hasattr(obj, "__xmlrename__")):
- for key, val in obj.__xmlrename__.iteritems():
- if (name == val):
- name = key
- break
-## if (name.startswith("__") and not name.endswith("__")):
-## name = "_%s%s" % (obj.__class__.__name__, name)
- return str(name)
-
-def printKnownTypes(kt, where):
- print 'KnownTypes from %s' % (where)
- for tag, cls in kt.iteritems():
- print '%s => %s' % (tag, str(cls))
-
-__typeMappingXsdToLang = {
- "string": "str",
- "char": "str",
- "varchar": "str",
- "date": "str", # ToDO Need to work out how to create lang date types
- "boolean": "bool",
- "decimal": "float", # ToDO Does python have a better fixed point type?
- "int": "int",
- "integer":"int",
- "long": "long",
- "float": "float",
- "bool": "bool",
- "str": "str",
- "unicode": "unicode",
- "short": "int",
- "duration": "str", # see above (date)
- "datetime": "str", # see above (date)
- "time": "str", # see above (date)
- "double": "float",
- "QName" : "str",
- "blob" : "str", # ag:blob
- "currency" : "str", # ag:currency
- }
-
-def xsdToLangType(xsdType):
- if xsdType.startswith(XMLSCHEMA_XSD_URL):
- xsdType = xsdType[len(XMLSCHEMA_XSD_URL)+1:]
- elif xsdType.startswith(AG_URL):
- xsdType = xsdType[len(AG_URL)+1:]
- langType = __typeMappingXsdToLang.get(xsdType)
- if (langType == None):
- raise Exception("Unknown xsd type %s" % xsdType)
- return langType
-
-def langToXsdType(langType):
- if langType in asDict(__typeMappingXsdToLang):
- return '%s:%s' % (XMLSCHEMA_XSD_URL, langType)
- return langType
-
-def _getXmlValue(langValue):
- if (isinstance(langValue, bool)):
- return str(langValue).lower()
- elif (isinstance(langValue, unicode)):
- return langValue.encode()
- else:
- return str(langValue)
-
-def unmarshal(xmlstr, knownTypes=None, knownNamespaces=None, xmlSource=None, createGenerics=False):
- objectfactory = XMLObjectFactory(knownTypes, knownNamespaces, xmlSource, createGenerics)
- # on Linux, pyXML's sax.parseString fails when passed unicode
- if (not sysutils.isWindows()):
- xmlstr = str(xmlstr)
- try:
- xml.sax.parseString(xmlstr, objectfactory)
- except xml.sax.SAXParseException, errorData:
- if xmlSource == None:
- xmlSource = 'unknown'
- errorString = 'SAXParseException ("%s") detected at line %d, column %d in XML document from source "%s" ' % (errorData.getMessage(), errorData.getLineNumber(), errorData.getColumnNumber(), xmlSource)
- raise UnmarshallerException(errorString)
- return objectfactory.getRootObject()
-
-def marshal(obj, elementName=None, prettyPrint=False, marshalType=True, indent=0, knownTypes=None, knownNamespaces=None, encoding=-1):
- worker = XMLMarshalWorker(prettyPrint=prettyPrint, marshalType=marshalType, knownTypes=knownTypes, knownNamespaces=knownNamespaces)
- if obj != None and hasattr(obj, '__xmldeepexclude__'):
- worker.xmldeepexclude = obj.__xmldeepexclude__
- xmlstr = "".join(worker._marshal(obj, elementName, indent=indent))
- aglogging.info(xmlMarshallerLogger, "marshal produced string of type %s", type(xmlstr))
- if (encoding == None):
- return xmlstr
- if (not isinstance(encoding, basestring)):
- encoding = sys.getdefaultencoding()
- if (not isinstance(xmlstr, unicode)):
- xmlstr = xmlstr.decode()
- xmlstr = u'<?xml version="1.0" encoding="%s"?>\n%s' % (encoding, xmlstr)
- return xmlstr.encode(encoding)
-
-class XMLMarshalWorker(object):
- def __init__(self, marshalType=True, prettyPrint=False, knownTypes=None, knownNamespaces=None):
- if knownTypes == None:
- self.knownTypes = {}
- else:
- self.knownTypes = knownTypes
- if knownNamespaces == None:
- self.knownNamespaces = {}
- else:
- self.knownNamespaces = knownNamespaces
- self.prettyPrint = prettyPrint
- self.marshalType = marshalType
- self.xmldeepexclude = []
- self.nsstack = []
-
- def getNSPrefix(self):
- if len(self.nsstack) > 0:
- return self.nsstack[-1].prefix
- return ''
-
- def isKnownType(self, elementName):
- tagLongNs = None
- nse = self.nsstack[-1]
- i = elementName.rfind(':')
- if i > 0:
- prefix = elementName[:i]
- name = elementName[i+1:]
- else:
- prefix = DEFAULT_NAMESPACE_KEY
- name = elementName
- for shortNs, longNs in nse.nameSpaces.iteritems():
- if shortNs == prefix:
- tagLongNs = longNs
- break
- if tagLongNs == None:
- knownTagName = elementName
- else:
- knownShortNs = self.knownNamespaces[tagLongNs]
- knownTagName = knownShortNs + ':' + name
- if (knownTagName in asDict(self.knownTypes)):
- knownClass = self.knownTypes[knownTagName]
- return True
- return False
-
- def popNSStack(self):
- self.nsstack.pop()
-
- def appendNSStack(self, obj):
- nameSpaces = {}
- defaultLongNS = None
- for nse in self.nsstack:
- for k, v in nse.nsMap.iteritems():
- nameSpaces[k] = v
- if k == DEFAULT_NAMESPACE_KEY:
- defaultLongNS = v
- newNS = NsElement()
- nameSpaceAttrs = ""
- if hasattr(obj, "__xmlnamespaces__"):
- ns = getattr(obj, "__xmlnamespaces__")
- keys = ns.keys()
- keys.sort()
- for nameSpaceKey in keys:
- nameSpaceUrl = ns[nameSpaceKey]
- if nameSpaceUrl in nameSpaces.values():
- for k, v in nameSpaces.iteritems():
- if v == nameSpaceUrl:
- nameSpaceKey = k
- break
- else:
- if nameSpaceKey == "":
- defaultLongNS = nameSpaceUrl
- nameSpaces[DEFAULT_NAMESPACE_KEY] = nameSpaceUrl
- newNS.nsMap[DEFAULT_NAMESPACE_KEY] = nameSpaceUrl
- nameSpaceAttrs += ' xmlns="%s" ' % (nameSpaceUrl)
- else:
- nameSpaces[nameSpaceKey] = nameSpaceUrl
- newNS.nsMap[nameSpaceKey] = nameSpaceUrl
- nameSpaceAttrs += ' xmlns:%s="%s" ' % (nameSpaceKey, nameSpaceUrl)
- nameSpaceAttrs = nameSpaceAttrs.rstrip()
- if len(self.nsstack) > 0:
- newNS.prefix = self.nsstack[-1].prefix
- else:
- newNS.prefix = ''
- if obj != None and hasattr(obj, "__xmldefaultnamespace__"):
- longPrefixNS = getattr(obj, "__xmldefaultnamespace__")
- if longPrefixNS == defaultLongNS:
- newNS.prefix = ''
- else:
- try:
- for k, v in nameSpaces.iteritems():
- if v == longPrefixNS:
- newNS.prefix = k + ':'
- break;
- except:
- if (longPrefixNS in asDict(self.knownNamespaces)):
- newNS.prefix = self.knownNamespaces[longPrefixNS] + ':'
- else:
- raise MarshallerException('Error marshalling __xmldefaultnamespace__ ("%s") not defined in namespace stack' % (longPrefixNS))
- if obj != None and hasattr(obj, "targetNamespace"):
- newNS.targetNS = obj.targetNamespace
- elif len(self.nsstack) > 0:
- newNS.targetNS = self.nsstack[-1].targetNS
- newNS.nameSpaces = nameSpaces
- self.nsstack.append(newNS)
- return nameSpaceAttrs
-
- def contractQName(self, value, obj, attr):
- value = langToXsdType(value)
- i = value.rfind(':')
- if i >= 0:
- longNS = value[:i]
- else:
- # the value doesn't have a namespace and we couldn't map it to an XSD type...what to do?
- # (a) just write it, as is, and hope it's in the default namespace (for now)
- # (b) throw an exception so we can track down the bad code (later)
- return value
- if (longNS in self.nsstack[-1].nameSpaces.values()):
- for kShort, vLong in self.nsstack[-1].nameSpaces.iteritems():
- if vLong == longNS:
- shortNS = kShort
- break
- else:
- shortNS = longNS # if we can't find the long->short mappping, just use longNS
- if shortNS == DEFAULT_NAMESPACE_KEY:
- value = value[i+1:]
- else:
- value = shortNS + ':' + value[i+1:]
- return value
-
- def _genObjTypeStr(self, typeString):
- if self.marshalType:
- return ' objtype="%s"' % typeString
- return ""
-
- def _marshal(self, obj, elementName=None, nameSpacePrefix="", indent=0):
- if (obj != None):
- aglogging.debug(xmlMarshallerLogger, "--> _marshal: elementName=%s%s, type=%s, obj=%s, indent=%d", nameSpacePrefix, elementName, type(obj), str(obj), indent)
- else:
- aglogging.debug(xmlMarshallerLogger, "--> _marshal: elementName=%s%s, obj is None, indent=%d", nameSpacePrefix, elementName, indent)
- if ((obj != None) and (hasattr(obj, 'preMarshal'))):
- obj.preMarshal()
- excludeAttrs = []
- excludeAttrs.extend(self.xmldeepexclude)
- if hasattr(obj, "__xmlexclude__"):
- excludeAttrs.extend(obj.__xmlexclude__)
- prettyPrint = self.prettyPrint
- knownTypes = self.knownTypes
- xmlString = None
- if self.prettyPrint or indent:
- prefix = " "*indent
- newline = "\n"
- increment = 2
- else:
- prefix = ""
- newline = ""
- increment = 0
- ## Determine the XML element name. If it isn"t specified in the
- ## parameter list, look for it in the __xmlname__ attribute,
- ## else use the default generic BASETYPE_ELEMENT_NAME.
- nameSpaceAttrs = self.appendNSStack(obj)
- nameSpacePrefix = self.getNSPrefix()
- if not elementName:
- if hasattr(obj, "__xmlname__"):
- elementName = nameSpacePrefix + obj.__xmlname__
- else:
- elementName = nameSpacePrefix + BASETYPE_ELEMENT_NAME
- else:
- elementName = nameSpacePrefix + elementName
-
- if (hasattr(obj, "__xmlsequencer__")) and (obj.__xmlsequencer__ != None):
- if (XMLSCHEMA_XSD_URL in self.nsstack[-1].nameSpaces.values()):
- for kShort, vLong in self.nsstack[-1].nameSpaces.iteritems():
- if vLong == XMLSCHEMA_XSD_URL:
- if kShort != DEFAULT_NAMESPACE_KEY:
- xsdPrefix = kShort + ':'
- else:
- xsdPrefix = ''
- break
- else:
- xsdPrefix = 'xs:'
- elementAdd = xsdPrefix + obj.__xmlsequencer__
- else:
- elementAdd = None
-
- members_to_skip = []
- ## Add more members_to_skip based on ones the user has selected
- ## via the __xmlexclude__ and __xmldeepexclude__ attributes.
- members_to_skip.extend(excludeAttrs)
- # Marshal the attributes that are selected to be XML attributes.
- objattrs = ""
- className = ag_className(obj)
- classNamePrefix = "_" + className
- if hasattr(obj, "__xmlattributes__"):
- xmlattributes = obj.__xmlattributes__
- members_to_skip.extend(xmlattributes)
- for attr in xmlattributes:
- internalAttrName = attr
- ifDefPy()
- if (attr.startswith("__") and not attr.endswith("__")):
- internalAttrName = classNamePrefix + attr
- endIfDef()
- # Fail silently if a python attribute is specified to be
- # an XML attribute but is missing.
- attrNameSpacePrefix = ""
- if hasattr(obj, "__xmlattrnamespaces__"):
- for nameSpaceKey, nameSpaceAttributes in getattr(obj, "__xmlattrnamespaces__").iteritems():
- if nameSpaceKey == nameSpacePrefix[:-1]: # Don't need to specify attribute namespace if it is the same as its element
- continue
- if attr in nameSpaceAttributes:
- attrNameSpacePrefix = nameSpaceKey + ":"
- break
- attrs = obj.__dict__
- value = attrs.get(internalAttrName)
- if (hasattr(obj, "__xmlrename__") and attr in asDict(obj.__xmlrename__)):
- attr = obj.__xmlrename__[attr]
- xsdElement = None
- complexType = getComplexType(obj)
- if (complexType != None):
- xsdElement = complexType.findElement(attr)
- if (xsdElement != None):
- default = xsdElement.default
- if (default != None):
- if ((default == value) or (default == _getXmlValue(value))):
- continue
- else:
- if (value == None):
- continue
- elif xsdElement.type == TYPE_QNAME:
- value = self.contractQName(value, obj, attr)
- elif value == None:
- continue
-
- # ToDO remove maxOccurs hack after bug 177 is fixed
- if attr == "maxOccurs" and value == -1:
- value = "unbounded"
-
- if isinstance(value, bool):
- if value == True:
- value = "true"
- else:
- value = "false"
- else:
- value = objutils.toDiffableRepr(value)
-
- objattrs += ' %s%s="%s"' % (attrNameSpacePrefix, attr, utillang.escape(value))
- if (obj == None):
- xmlString = [""]
- elif isinstance(obj, bool):
- objTypeStr = self._genObjTypeStr("bool")
- xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, obj, elementName, newline)]
- elif isinstance(obj, int):
- objTypeStr = self._genObjTypeStr("int")
- xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)]
- elif isinstance(obj, long):
- objTypeStr = self._genObjTypeStr("long")
- xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)]
- elif isinstance(obj, float):
- objTypeStr = self._genObjTypeStr("float")
- xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)]
- elif isinstance(obj, unicode): # have to check before basestring - unicode is instance of base string
- xmlString = ['%s<%s>%s</%s>%s' % (prefix, elementName, utillang.escape(obj.encode()), elementName, newline)]
- elif isinstance(obj, basestring):
- xmlString = ['%s<%s>%s</%s>%s' % (prefix, elementName, utillang.escape(obj), elementName, newline)]
- elif isinstance(obj, datetime.datetime):
- objTypeStr = self._genObjTypeStr("datetime")
- xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)]
- elif isinstance(obj, datetime.date):
- objTypeStr = self._genObjTypeStr("date")
- xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)]
- elif isinstance(obj, datetime.time):
- objTypeStr = self._genObjTypeStr("time")
- xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)]
- elif isinstance(obj, list):
- if len(obj) < 1:
- xmlString = ""
- else:
- objTypeStr = self._genObjTypeStr("list")
- xmlString = ['%s<%s%s>%s' % (prefix, elementName, objTypeStr, newline)]
- for item in obj:
- xmlString.extend(self._marshal(item, indent=indent+increment))
- xmlString.append("%s</%s>%s" % (prefix, elementName, newline))
- elif isinstance(obj, tuple):
- if len(obj) < 1:
- xmlString = ""
- else:
- objTypeStr = self._genObjTypeStr("list")
- xmlString = ['%s<%s%s mutable="false">%s' % (prefix, elementName, objTypeStr, newline)]
- for item in obj:
- xmlString.extend(self._marshal(item, indent=indent+increment))
- xmlString.append("%s</%s>%s" % (prefix, elementName, newline))
- elif isinstance(obj, dict):
- objTypeStr = self._genObjTypeStr("dict")
- xmlString = ['%s<%s%s>%s' % (prefix, elementName, objTypeStr, newline)]
- subprefix = prefix + " "*increment
- subindent = indent + 2*increment
- keys = obj.keys()
- keys.sort()
- for key in keys:
- xmlString.append("%s<%s>%s" % (subprefix, DICT_ITEM_NAME, newline))
- xmlString.extend(self._marshal(key, elementName=DICT_ITEM_KEY_NAME, indent=subindent))
- xmlString.extend(self._marshal(obj[key], elementName=DICT_ITEM_VALUE_NAME, indent=subindent))
- xmlString.append("%s</%s>%s" % (subprefix, DICT_ITEM_NAME, newline))
- xmlString.append("%s</%s>%s" % (prefix, elementName, newline))
- elif hasattr(obj, "__xmlcontent__"):
- contentValue = getattr(obj, obj.__xmlcontent__)
- if contentValue == None:
- xmlString = ["%s<%s%s%s/>%s" % (prefix, elementName, nameSpaceAttrs, objattrs, newline)]
- else:
- contentValue = utillang.escape(contentValue)
- xmlString = ["%s<%s%s%s>%s</%s>%s" % (prefix, elementName, nameSpaceAttrs, objattrs, contentValue, elementName, newline)]
- else:
- # Only add the objtype if the element tag is unknown to us.
- if (isinstance(obj, GenericXMLObject)):
- objTypeStr = ""
- elif (self.isKnownType(elementName) == True):
- objTypeStr = ""
- else:
- objTypeStr = self._genObjTypeStr("%s.%s" % (obj.__class__.__module__, className))
- xmlString = ['%s<%s%s%s%s' % (prefix, elementName, nameSpaceAttrs, objattrs, objTypeStr)]
- # get the member, value pairs for the object, filtering out the types we don"t support
- if (elementAdd != None):
- prefix += increment*" "
- indent += increment
- xmlMemberString = []
- if hasattr(obj, "__xmlbody__"):
- xmlbody = getattr(obj, obj.__xmlbody__)
- if xmlbody != None:
- xmlMemberString.append(utillang.escape(xmlbody))
- else:
- if hasattr(obj, "__xmlattrgroups__"):
- attrGroups = obj.__xmlattrgroups__.copy()
- if (not isinstance(attrGroups, dict)):
- raise "__xmlattrgroups__ is not a dict, but must be"
- for n in attrGroups.iterkeys():
- members_to_skip.extend(attrGroups[n])
- else:
- attrGroups = {}
- # add the list of all attributes to attrGroups
- eList = obj.__dict__.keys()
- eList.sort()
- attrGroups["__nogroup__"] = eList
-
- for eName, eList in attrGroups.iteritems():
- if (eName != "__nogroup__"):
- prefix += increment*" "
- indent += increment
- objTypeStr = self._genObjTypeStr("None")
- xmlMemberString.append('%s<%s%s>%s' % (prefix, eName, objTypeStr, newline))
- for name in eList:
- value = obj.__dict__[name]
- if eName == "__nogroup__" and name in members_to_skip: continue
- if name.startswith("__") and name.endswith("__"): continue
- if (hasattr(obj, "__xmlcdatacontent__") and (obj.__xmlcdatacontent__ == name)):
- continue
- subElementNameSpacePrefix = nameSpacePrefix
- if hasattr(obj, "__xmlattrnamespaces__"):
- for nameSpaceKey, nameSpaceValues in getattr(obj, "__xmlattrnamespaces__").iteritems():
- if name in nameSpaceValues:
- subElementNameSpacePrefix = nameSpaceKey + ":"
- break
- # handle sequences listed in __xmlflattensequence__
- # specially: instead of listing the contained items inside
- # of a separate list, as God intended, list them inside
- # the object containing the sequence.
- if (hasattr(obj, "__xmlflattensequence__") and (value != None) and (name in asDict(obj.__xmlflattensequence__))):
- xmlnametuple = obj.__xmlflattensequence__[name]
- if (xmlnametuple == None):
- xmlnametuple = [name]
- elif (not isinstance(xmlnametuple, (tuple,list))):
- xmlnametuple = [str(xmlnametuple)]
- xmlname = None
- if (len(xmlnametuple) == 1):
- xmlname = xmlnametuple[0]
- if not isinstance(value, (list, tuple)):
- value = [value]
- for seqitem in value:
- xmlMemberString.extend(self._marshal(seqitem, xmlname, subElementNameSpacePrefix, indent=indent+increment))
- else:
- if (hasattr(obj, "__xmlrename__") and name in asDict(obj.__xmlrename__)):
- xmlname = obj.__xmlrename__[name]
- else:
- xmlname = name
- if (value != None):
- xmlMemberString.extend(self._marshal(value, xmlname, subElementNameSpacePrefix, indent=indent+increment))
- if (eName != "__nogroup__"):
- xmlMemberString.append("%s</%s>%s" % (prefix, eName, newline))
- prefix = prefix[:-increment]
- indent -= increment
-
- # if we have nested elements, add them here, otherwise close the element tag immediately.
- newList = []
- for s in xmlMemberString:
- if (len(s) > 0): newList.append(s)
- xmlMemberString = newList
- if len(xmlMemberString) > 0:
- xmlString.append(">")
- if hasattr(obj, "__xmlbody__"):
- xmlString.extend(xmlMemberString)
- xmlString.append("</%s>%s" % (elementName, newline))
- else:
- xmlString.append(newline)
- if (elementAdd != None):
- xmlString.append("%s<%s>%s" % (prefix, elementAdd, newline))
- xmlString.extend(xmlMemberString)
- if (elementAdd != None):
- xmlString.append("%s</%s>%s" % (prefix, elementAdd, newline))
- prefix = prefix[:-increment]
- indent -= increment
- xmlString.append("%s</%s>%s" % (prefix, elementName, newline))
- else:
- if hasattr(obj, "__xmlcdatacontent__"):
- cdataAttr = obj.__xmlcdatacontent__
- cdataContent = obj.__dict__[cdataAttr]
- xmlString.append("><![CDATA[%s]]></%s>%s" % (cdataContent, elementName, newline))
- else:
- xmlString.append("/>%s" % newline)
- if aglogging.isEnabledForDebug(xmlMarshallerLogger):
- aglogging.debug(xmlMarshallerLogger, "<-- _marshal: %s", objutils.toDiffableString(xmlString))
- #print "<-- _marshal: %s" % str(xmlString)
- self.popNSStack()
- return xmlString
-
-# A simple test, to be executed when the xmlmarshaller is run standalone
-class MarshallerPerson:
- __xmlname__ = "person"
- __xmlexclude__ = ["fabulousness",]
- __xmlattributes__ = ("nonSmoker",)
- __xmlrename__ = {"_phoneNumber": "telephone"}
- __xmlflattensequence__ = {"favoriteWords": ("vocabulary",)}
- __xmlattrgroups__ = {"name": ["firstName", "lastName"], "address": ["addressLine1", "city", "state", "zip"]}
-
- def setPerson(self):
- self.firstName = "Albert"
- self.lastName = "Camus"
- self.addressLine1 = "23 Absurd St."
- self.city = "Ennui"
- self.state = "MO"
- self.zip = "54321"
- self._phoneNumber = "808-303-2323"
- self.favoriteWords = ["angst", "ennui", "existence"]
- self.phobias = ["war", "tuberculosis", "cars"]
- self.weight = 150
- self.fabulousness = "tres tres"
- self.nonSmoker = False
-
-if isMain(__name__):
- p1 = MarshallerPerson()
- p1.setPerson()
- xmlP1 = marshal(p1, prettyPrint=True, encoding="utf-8")
- print "\n########################"
- print "# testPerson test case #"
- print "########################"
- print xmlP1
- p2 = unmarshal(xmlP1)
- xmlP2 = marshal(p2, prettyPrint=True, encoding="utf-8")
- if xmlP1 == xmlP2:
- print "Success: repeated marshalling yields identical results"
- else:
- print "Failure: repeated marshalling yields different results"
- print xmlP2