Libraries for Subliminal

This commit is contained in:
Ruud
2011-12-23 16:02:48 +01:00
parent a03c33c1c3
commit 8efc88e691
109 changed files with 26894 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the (LGPL) GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library Lesser General Public License for more details at
# ( http://www.gnu.org/licenses/lgpl.html ).
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# written by: Jeff Ortel ( jortel@redhat.com )
"""
Provides modules containing classes to support
marshalling (XML).
"""
from suds.sudsobject import Object
class Content(Object):
"""
Marshaller Content.
@ivar tag: The content tag.
@type tag: str
@ivar value: The content's value.
@type value: I{any}
"""
extensions = []
def __init__(self, tag=None, value=None, **kwargs):
"""
@param tag: The content tag.
@type tag: str
@param value: The content's value.
@type value: I{any}
"""
Object.__init__(self)
self.tag = tag
self.value = value
for k,v in kwargs.items():
setattr(self, k, v)
def __getattr__(self, name):
if name not in self.__dict__:
if name in self.extensions:
v = None
setattr(self, name, v)
else:
raise AttributeError, \
'Content has no attribute %s' % name
else:
v = self.__dict__[name]
return v
+316
View File
@@ -0,0 +1,316 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the (LGPL) GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library Lesser General Public License for more details at
# ( http://www.gnu.org/licenses/lgpl.html ).
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# written by: Jeff Ortel ( jortel@redhat.com )
"""
Provides appender classes for I{marshalling}.
"""
from logging import getLogger
from suds import *
from suds.mx import *
from suds.sudsobject import footprint
from suds.sudsobject import Object, Property
from suds.sax.element import Element
from suds.sax.text import Text
from copy import deepcopy
log = getLogger(__name__)
class Matcher:
"""
Appender matcher.
@ivar cls: A class object.
@type cls: I{classobj}
"""
def __init__(self, cls):
"""
@param cls: A class object.
@type cls: I{classobj}
"""
self.cls = cls
def __eq__(self, x):
if self.cls is None:
return ( x is None )
else:
return isinstance(x, self.cls)
class ContentAppender:
"""
Appender used to add content to marshalled objects.
@ivar default: The default appender.
@type default: L{Appender}
@ivar appenders: A I{table} of appenders mapped by class.
@type appenders: I{table}
"""
def __init__(self, marshaller):
"""
@param marshaller: A marshaller.
@type marshaller: L{suds.mx.core.Core}
"""
self.default = PrimativeAppender(marshaller)
self.appenders = (
(Matcher(None),
NoneAppender(marshaller)),
(Matcher(null),
NoneAppender(marshaller)),
(Matcher(Property),
PropertyAppender(marshaller)),
(Matcher(Object),
ObjectAppender(marshaller)),
(Matcher(Element),
ElementAppender(marshaller)),
(Matcher(Text),
TextAppender(marshaller)),
(Matcher(list),
ListAppender(marshaller)),
(Matcher(tuple),
ListAppender(marshaller)),
(Matcher(dict),
DictAppender(marshaller)),
)
def append(self, parent, content):
"""
Select an appender and append the content to parent.
@param parent: A parent node.
@type parent: L{Element}
@param content: The content to append.
@type content: L{Content}
"""
appender = self.default
for a in self.appenders:
if a[0] == content.value:
appender = a[1]
break
appender.append(parent, content)
class Appender:
"""
An appender used by the marshaller to append content.
@ivar marshaller: A marshaller.
@type marshaller: L{suds.mx.core.Core}
"""
def __init__(self, marshaller):
"""
@param marshaller: A marshaller.
@type marshaller: L{suds.mx.core.Core}
"""
self.marshaller = marshaller
def node(self, content):
"""
Create and return an XML node that is qualified
using the I{type}. Also, make sure all referenced namespace
prefixes are declared.
@param content: The content for which proccessing has ended.
@type content: L{Object}
@return: A new node.
@rtype: L{Element}
"""
return self.marshaller.node(content)
def setnil(self, node, content):
"""
Set the value of the I{node} to nill.
@param node: A I{nil} node.
@type node: L{Element}
@param content: The content for which proccessing has ended.
@type content: L{Object}
"""
self.marshaller.setnil(node, content)
def setdefault(self, node, content):
"""
Set the value of the I{node} to a default value.
@param node: A I{nil} node.
@type node: L{Element}
@param content: The content for which proccessing has ended.
@type content: L{Object}
@return: The default.
"""
return self.marshaller.setdefault(node, content)
def optional(self, content):
"""
Get whether the specified content is optional.
@param content: The content which to check.
@type content: L{Content}
"""
return self.marshaller.optional(content)
def suspend(self, content):
"""
Notify I{marshaller} that appending this content has suspended.
@param content: The content for which proccessing has been suspended.
@type content: L{Object}
"""
self.marshaller.suspend(content)
def resume(self, content):
"""
Notify I{marshaller} that appending this content has resumed.
@param content: The content for which proccessing has been resumed.
@type content: L{Object}
"""
self.marshaller.resume(content)
def append(self, parent, content):
"""
Append the specified L{content} to the I{parent}.
@param content: The content to append.
@type content: L{Object}
"""
self.marshaller.append(parent, content)
class PrimativeAppender(Appender):
"""
An appender for python I{primative} types.
"""
def append(self, parent, content):
if content.tag.startswith('_'):
attr = content.tag[1:]
value = tostr(content.value)
if value:
parent.set(attr, value)
else:
child = self.node(content)
child.setText(tostr(content.value))
parent.append(child)
class NoneAppender(Appender):
"""
An appender for I{None} values.
"""
def append(self, parent, content):
child = self.node(content)
default = self.setdefault(child, content)
if default is None:
self.setnil(child, content)
parent.append(child)
class PropertyAppender(Appender):
"""
A L{Property} appender.
"""
def append(self, parent, content):
p = content.value
child = self.node(content)
child.setText(p.get())
parent.append(child)
for item in p.items():
cont = Content(tag=item[0], value=item[1])
Appender.append(self, child, cont)
class ObjectAppender(Appender):
"""
An L{Object} appender.
"""
def append(self, parent, content):
object = content.value
if self.optional(content) and footprint(object) == 0:
return
child = self.node(content)
parent.append(child)
for item in object:
cont = Content(tag=item[0], value=item[1])
Appender.append(self, child, cont)
class DictAppender(Appender):
"""
An python I{dict} appender.
"""
def append(self, parent, content):
d = content.value
if self.optional(content) and len(d) == 0:
return
child = self.node(content)
parent.append(child)
for item in d.items():
cont = Content(tag=item[0], value=item[1])
Appender.append(self, child, cont)
class ElementWrapper(Element):
"""
Element wrapper.
"""
def __init__(self, content):
Element.__init__(self, content.name, content.parent)
self.__content = content
def str(self, indent=0):
return self.__content.str(indent)
class ElementAppender(Appender):
"""
An appender for I{Element} types.
"""
def append(self, parent, content):
if content.tag.startswith('_'):
raise Exception('raw XML not valid as attribute value')
child = ElementWrapper(content.value)
parent.append(child)
class ListAppender(Appender):
"""
A list/tuple appender.
"""
def append(self, parent, content):
collection = content.value
if len(collection):
self.suspend(content)
for item in collection:
cont = Content(tag=content.tag, value=item)
Appender.append(self, parent, cont)
self.resume(content)
class TextAppender(Appender):
"""
An appender for I{Text} values.
"""
def append(self, parent, content):
if content.tag.startswith('_'):
attr = content.tag[1:]
value = tostr(content.value)
if value:
parent.set(attr, value)
else:
child = self.node(content)
child.setText(content.value)
parent.append(child)
+48
View File
@@ -0,0 +1,48 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the (LGPL) GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library Lesser General Public License for more details at
# ( http://www.gnu.org/licenses/lgpl.html ).
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# written by: Jeff Ortel ( jortel@redhat.com )
"""
Provides basic I{marshaller} classes.
"""
from logging import getLogger
from suds import *
from suds.mx import *
from suds.mx.core import Core
log = getLogger(__name__)
class Basic(Core):
"""
A I{basic} (untyped) marshaller.
"""
def process(self, value, tag=None):
"""
Process (marshal) the tag with the specified value using the
optional type information.
@param value: The value (content) of the XML node.
@type value: (L{Object}|any)
@param tag: The (optional) tag name for the value. The default is
value.__class__.__name__
@type tag: str
@return: An xml node.
@rtype: L{Element}
"""
content = Content(tag=tag, value=value)
result = Core.process(self, content)
return result
+158
View File
@@ -0,0 +1,158 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the (LGPL) GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library Lesser General Public License for more details at
# ( http://www.gnu.org/licenses/lgpl.html ).
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# written by: Jeff Ortel ( jortel@redhat.com )
"""
Provides I{marshaller} core classes.
"""
from logging import getLogger
from suds import *
from suds.mx import *
from suds.mx.appender import ContentAppender
from suds.sax.element import Element
from suds.sax.document import Document
from suds.sudsobject import Property
log = getLogger(__name__)
class Core:
"""
An I{abstract} marshaller. This class implement the core
functionality of the marshaller.
@ivar appender: A content appender.
@type appender: L{ContentAppender}
"""
def __init__(self):
"""
"""
self.appender = ContentAppender(self)
def process(self, content):
"""
Process (marshal) the tag with the specified value using the
optional type information.
@param content: The content to process.
@type content: L{Object}
"""
log.debug('processing:\n%s', content)
self.reset()
if content.tag is None:
content.tag = content.value.__class__.__name__
document = Document()
if isinstance(content.value, Property):
root = self.node(content)
self.append(document, content)
else:
self.append(document, content)
return document.root()
def append(self, parent, content):
"""
Append the specified L{content} to the I{parent}.
@param parent: The parent node to append to.
@type parent: L{Element}
@param content: The content to append.
@type content: L{Object}
"""
log.debug('appending parent:\n%s\ncontent:\n%s', parent, content)
if self.start(content):
self.appender.append(parent, content)
self.end(parent, content)
def reset(self):
"""
Reset the marshaller.
"""
pass
def node(self, content):
"""
Create and return an XML node.
@param content: The content for which proccessing has been suspended.
@type content: L{Object}
@return: An element.
@rtype: L{Element}
"""
return Element(content.tag)
def start(self, content):
"""
Appending this content has started.
@param content: The content for which proccessing has started.
@type content: L{Content}
@return: True to continue appending
@rtype: boolean
"""
return True
def suspend(self, content):
"""
Appending this content has suspended.
@param content: The content for which proccessing has been suspended.
@type content: L{Content}
"""
pass
def resume(self, content):
"""
Appending this content has resumed.
@param content: The content for which proccessing has been resumed.
@type content: L{Content}
"""
pass
def end(self, parent, content):
"""
Appending this content has ended.
@param parent: The parent node ending.
@type parent: L{Element}
@param content: The content for which proccessing has ended.
@type content: L{Content}
"""
pass
def setnil(self, node, content):
"""
Set the value of the I{node} to nill.
@param node: A I{nil} node.
@type node: L{Element}
@param content: The content to set nil.
@type content: L{Content}
"""
pass
def setdefault(self, node, content):
"""
Set the value of the I{node} to a default value.
@param node: A I{nil} node.
@type node: L{Element}
@param content: The content to set the default value.
@type content: L{Content}
@return: The default.
"""
pass
def optional(self, content):
"""
Get whether the specified content is optional.
@param content: The content which to check.
@type content: L{Content}
"""
return False
+133
View File
@@ -0,0 +1,133 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the (LGPL) GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library Lesser General Public License for more details at
# ( http://www.gnu.org/licenses/lgpl.html ).
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# written by: Jeff Ortel ( jortel@redhat.com )
"""
Provides encoded I{marshaller} classes.
"""
from logging import getLogger
from suds import *
from suds.mx import *
from suds.mx.literal import Literal
from suds.mx.typer import Typer
from suds.sudsobject import Factory, Object
from suds.xsd.query import TypeQuery
log = getLogger(__name__)
#
# Add encoded extensions
# aty = The soap (section 5) encoded array type.
#
Content.extensions.append('aty')
class Encoded(Literal):
"""
A SOAP section (5) encoding marshaller.
This marshaller supports rpc/encoded soap styles.
"""
def start(self, content):
#
# For soap encoded arrays, the 'aty' (array type) information
# is extracted and added to the 'content'. Then, the content.value
# is replaced with an object containing an 'item=[]' attribute
# containing values that are 'typed' suds objects.
#
start = Literal.start(self, content)
if start and isinstance(content.value, (list,tuple)):
resolved = content.type.resolve()
for c in resolved:
if hasattr(c[0], 'aty'):
content.aty = (content.tag, c[0].aty)
self.cast(content)
break
return start
def end(self, parent, content):
#
# For soap encoded arrays, the soapenc:arrayType attribute is
# added with proper type and size information.
# Eg: soapenc:arrayType="xs:int[3]"
#
Literal.end(self, parent, content)
if content.aty is None:
return
tag, aty = content.aty
ns0 = ('at0', aty[1])
ns1 = ('at1', 'http://schemas.xmlsoap.org/soap/encoding/')
array = content.value.item
child = parent.getChild(tag)
child.addPrefix(ns0[0], ns0[1])
child.addPrefix(ns1[0], ns1[1])
name = '%s:arrayType' % ns1[0]
value = '%s:%s[%d]' % (ns0[0], aty[0], len(array))
child.set(name, value)
def encode(self, node, content):
if content.type.any():
Typer.auto(node, content.value)
return
if content.real.any():
Typer.auto(node, content.value)
return
ns = None
name = content.real.name
if self.xstq:
ns = content.real.namespace()
Typer.manual(node, name, ns)
def cast(self, content):
"""
Cast the I{untyped} list items found in content I{value}.
Each items contained in the list is checked for XSD type information.
Items (values) that are I{untyped}, are replaced with suds objects and
type I{metadata} is added.
@param content: The content holding the collection.
@type content: L{Content}
@return: self
@rtype: L{Encoded}
"""
aty = content.aty[1]
resolved = content.type.resolve()
array = Factory.object(resolved.name)
array.item = []
query = TypeQuery(aty)
ref = query.execute(self.schema)
if ref is None:
raise TypeNotFound(qref)
for x in content.value:
if isinstance(x, (list, tuple)):
array.item.append(x)
continue
if isinstance(x, Object):
md = x.__metadata__
md.sxtype = ref
array.item.append(x)
continue
if isinstance(x, dict):
x = Factory.object(ref.name, x)
md = x.__metadata__
md.sxtype = ref
array.item.append(x)
continue
x = Factory.property(ref.name, x)
md = x.__metadata__
md.sxtype = ref
array.item.append(x)
content.value = array
return self
+291
View File
@@ -0,0 +1,291 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the (LGPL) GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library Lesser General Public License for more details at
# ( http://www.gnu.org/licenses/lgpl.html ).
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# written by: Jeff Ortel ( jortel@redhat.com )
"""
Provides literal I{marshaller} classes.
"""
from logging import getLogger
from suds import *
from suds.mx import *
from suds.mx.core import Core
from suds.mx.typer import Typer
from suds.resolver import GraphResolver, Frame
from suds.sax.element import Element
from suds.sudsobject import Factory
log = getLogger(__name__)
#
# Add typed extensions
# type = The expected xsd type
# real = The 'true' XSD type
# ancestry = The 'type' ancestry
#
Content.extensions.append('type')
Content.extensions.append('real')
Content.extensions.append('ancestry')
class Typed(Core):
"""
A I{typed} marshaller.
This marshaller is semi-typed as needed to support both
I{document/literal} and I{rpc/literal} soap message styles.
@ivar schema: An xsd schema.
@type schema: L{xsd.schema.Schema}
@ivar resolver: A schema type resolver.
@type resolver: L{GraphResolver}
"""
def __init__(self, schema, xstq=True):
"""
@param schema: A schema object
@type schema: L{xsd.schema.Schema}
@param xstq: The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates
that the I{xsi:type} attribute values should be qualified by namespace.
@type xstq: bool
"""
Core.__init__(self)
self.schema = schema
self.xstq = xstq
self.resolver = GraphResolver(self.schema)
def reset(self):
self.resolver.reset()
def start(self, content):
#
# Start marshalling the 'content' by ensuring that both the
# 'content' _and_ the resolver are primed with the XSD type
# information. The 'content' value is both translated and
# sorted based on the XSD type. Only values that are objects
# have their attributes sorted.
#
log.debug('starting content:\n%s', content)
if content.type is None:
name = content.tag
if name.startswith('_'):
name = '@'+name[1:]
content.type = self.resolver.find(name, content.value)
if content.type is None:
raise TypeNotFound(content.tag)
else:
known = None
if isinstance(content.value, Object):
known = self.resolver.known(content.value)
if known is None:
log.debug('object has no type information', content.value)
known = content.type
frame = Frame(content.type, resolved=known)
self.resolver.push(frame)
frame = self.resolver.top()
content.real = frame.resolved
content.ancestry = frame.ancestry
self.translate(content)
self.sort(content)
if self.skip(content):
log.debug('skipping (optional) content:\n%s', content)
self.resolver.pop()
return False
else:
return True
def suspend(self, content):
#
# Suspend to process a list content. Primarily, this
# involves popping the 'list' content off the resolver's
# stack so the list items can be marshalled.
#
self.resolver.pop()
def resume(self, content):
#
# Resume processing a list content. To do this, we
# really need to simply push the 'list' content
# back onto the resolver stack.
#
self.resolver.push(Frame(content.type))
def end(self, parent, content):
#
# End processing the content. Make sure the content
# ending matches the top of the resolver stack since for
# list processing we play games with the resolver stack.
#
log.debug('ending content:\n%s', content)
current = self.resolver.top().type
if current == content.type:
self.resolver.pop()
else:
raise Exception, \
'content (end) mismatch: top=(%s) cont=(%s)' % \
(current, content)
def node(self, content):
#
# Create an XML node and namespace qualify as defined
# by the schema (elementFormDefault).
#
ns = content.type.namespace()
if content.type.form_qualified:
node = Element(content.tag, ns=ns)
node.addPrefix(ns[0], ns[1])
else:
node = Element(content.tag)
self.encode(node, content)
log.debug('created - node:\n%s', node)
return node
def setnil(self, node, content):
#
# Set the 'node' nil only if the XSD type
# specifies that it is permitted.
#
if content.type.nillable:
node.setnil()
def setdefault(self, node, content):
#
# Set the node to the default value specified
# by the XSD type.
#
default = content.type.default
if default is None:
pass
else:
node.setText(default)
return default
def optional(self, content):
if content.type.optional():
return True
for a in content.ancestry:
if a.optional():
return True
return False
def encode(self, node, content):
# Add (soap) encoding information only if the resolved
# type is derived by extension. Further, the xsi:type values
# is qualified by namespace only if the content (tag) and
# referenced type are in different namespaces.
if content.type.any():
return
if not content.real.extension():
return
if content.type.resolve() == content.real:
return
ns = None
name = content.real.name
if self.xstq:
ns = content.real.namespace('ns1')
Typer.manual(node, name, ns)
def skip(self, content):
"""
Get whether to skip this I{content}.
Should be skipped when the content is optional
and either the value=None or the value is an empty list.
@param content: The content to skip.
@type content: L{Object}
@return: True if content is to be skipped.
@rtype: bool
"""
if self.optional(content):
v = content.value
if v is None:
return True
if isinstance(v, (list,tuple)) and len(v) == 0:
return True
return False
def optional(self, content):
if content.type.optional():
return True
for a in content.ancestry:
if a.optional():
return True
return False
def translate(self, content):
"""
Translate using the XSD type information.
Python I{dict} is translated to a suds object. Most
importantly, primative values are translated from python
types to XML types using the XSD type.
@param content: The content to translate.
@type content: L{Object}
@return: self
@rtype: L{Typed}
"""
v = content.value
if v is None:
return
if isinstance(v, dict):
cls = content.real.name
content.value = Factory.object(cls, v)
md = content.value.__metadata__
md.sxtype = content.type
return
v = content.real.translate(v, False)
content.value = v
return self
def sort(self, content):
"""
Sort suds object attributes based on ordering defined
in the XSD type information.
@param content: The content to sort.
@type content: L{Object}
@return: self
@rtype: L{Typed}
"""
v = content.value
if isinstance(v, Object):
md = v.__metadata__
md.ordering = self.ordering(content.real)
return self
def ordering(self, type):
"""
Get the attribute ordering defined in the specified
XSD type information.
@param type: An XSD type object.
@type type: SchemaObject
@return: An ordered list of attribute names.
@rtype: list
"""
result = []
for child, ancestry in type.resolve():
name = child.name
if child.name is None:
continue
if child.isattr():
name = '_%s' % child.name
result.append(name)
return result
class Literal(Typed):
"""
A I{literal} marshaller.
This marshaller is semi-typed as needed to support both
I{document/literal} and I{rpc/literal} soap message styles.
"""
pass
+123
View File
@@ -0,0 +1,123 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the (LGPL) GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library Lesser General Public License for more details at
# ( http://www.gnu.org/licenses/lgpl.html ).
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# written by: Jeff Ortel ( jortel@redhat.com )
"""
Provides sx typing classes.
"""
from logging import getLogger
from suds import *
from suds.mx import *
from suds.sax import Namespace as NS
from suds.sax.text import Text
log = getLogger(__name__)
class Typer:
"""
Provides XML node typing as either automatic or manual.
@cvar types: A dict of class to xs type mapping.
@type types: dict
"""
types = {
int : ('int', NS.xsdns),
long : ('long', NS.xsdns),
float : ('float', NS.xsdns),
str : ('string', NS.xsdns),
unicode : ('string', NS.xsdns),
Text : ('string', NS.xsdns),
bool : ('boolean', NS.xsdns),
}
@classmethod
def auto(cls, node, value=None):
"""
Automatically set the node's xsi:type attribute based on either I{value}'s
class or the class of the node's text. When I{value} is an unmapped class,
the default type (xs:any) is set.
@param node: An XML node
@type node: L{sax.element.Element}
@param value: An object that is or would be the node's text.
@type value: I{any}
@return: The specified node.
@rtype: L{sax.element.Element}
"""
if value is None:
value = node.getText()
if isinstance(value, Object):
known = cls.known(value)
if known.name is None:
return node
tm = (known.name, known.namespace())
else:
tm = cls.types.get(value.__class__, cls.types.get(str))
cls.manual(node, *tm)
return node
@classmethod
def manual(cls, node, tval, ns=None):
"""
Set the node's xsi:type attribute based on either I{value}'s
class or the class of the node's text. Then adds the referenced
prefix(s) to the node's prefix mapping.
@param node: An XML node
@type node: L{sax.element.Element}
@param tval: The name of the schema type.
@type tval: str
@param ns: The XML namespace of I{tval}.
@type ns: (prefix, uri)
@return: The specified node.
@rtype: L{sax.element.Element}
"""
xta = ':'.join((NS.xsins[0], 'type'))
node.addPrefix(NS.xsins[0], NS.xsins[1])
if ns is None:
node.set(xta, tval)
else:
ns = cls.genprefix(node, ns)
qname = ':'.join((ns[0], tval))
node.set(xta, qname)
node.addPrefix(ns[0], ns[1])
return node
@classmethod
def genprefix(cls, node, ns):
"""
Generate a prefix.
@param node: An XML node on which the prefix will be used.
@type node: L{sax.element.Element}
@param ns: A namespace needing an unique prefix.
@type ns: (prefix, uri)
@return: The I{ns} with a new prefix.
"""
for n in range(1, 1024):
p = 'ns%d' % n
u = node.resolvePrefix(p, default=None)
if u is None or u == ns[1]:
return (p, ns[1])
raise Exception('auto prefix, exhausted')
@classmethod
def known(cls, object):
try:
md = object.__metadata__
known = md.sxtype
return known
except:
pass