Libraries for Subliminal
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# 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 )
|
||||
|
||||
"""
|
||||
The I{schema} module provides a intelligent representation of
|
||||
an XSD schema. The I{raw} model is the XML tree and the I{model}
|
||||
is the denormalized, objectified and intelligent view of the schema.
|
||||
Most of the I{value-add} provided by the model is centered around
|
||||
tranparent referenced type resolution and targeted denormalization.
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
from suds import *
|
||||
from suds.sax import Namespace, splitPrefix
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
def qualify(ref, resolvers, defns=Namespace.default):
|
||||
"""
|
||||
Get a reference that is I{qualified} by namespace.
|
||||
@param ref: A referenced schema type name.
|
||||
@type ref: str
|
||||
@param resolvers: A list of objects to be used to resolve types.
|
||||
@type resolvers: [L{sax.element.Element},]
|
||||
@param defns: An optional target namespace used to qualify references
|
||||
when no prefix is specified.
|
||||
@type defns: A default namespace I{tuple: (prefix,uri)} used when ref not prefixed.
|
||||
@return: A qualified reference.
|
||||
@rtype: (name, namespace-uri)
|
||||
"""
|
||||
ns = None
|
||||
p, n = splitPrefix(ref)
|
||||
if p is not None:
|
||||
if not isinstance(resolvers, (list, tuple)):
|
||||
resolvers = (resolvers,)
|
||||
for r in resolvers:
|
||||
resolved = r.resolvePrefix(p)
|
||||
if resolved[1] is not None:
|
||||
ns = resolved
|
||||
break
|
||||
if ns is None:
|
||||
raise Exception('prefix (%s) not resolved' % p)
|
||||
else:
|
||||
ns = defns
|
||||
return (n, ns[1])
|
||||
|
||||
def isqref(object):
|
||||
"""
|
||||
Get whether the object is a I{qualified reference}.
|
||||
@param object: An object to be tested.
|
||||
@type object: I{any}
|
||||
@rtype: boolean
|
||||
@see: L{qualify}
|
||||
"""
|
||||
return (\
|
||||
isinstance(object, tuple) and \
|
||||
len(object) == 2 and \
|
||||
isinstance(object[0], basestring) and \
|
||||
isinstance(object[1], basestring))
|
||||
|
||||
|
||||
class Filter:
|
||||
def __init__(self, inclusive=False, *items):
|
||||
self.inclusive = inclusive
|
||||
self.items = items
|
||||
def __contains__(self, x):
|
||||
if self.inclusive:
|
||||
result = ( x in self.items )
|
||||
else:
|
||||
result = ( x not in self.items )
|
||||
return result
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
# 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 )
|
||||
|
||||
"""
|
||||
The I{depsolve} module defines a class for performing dependancy solving.
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
from suds import *
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class DepList:
|
||||
"""
|
||||
Dependancy solving list.
|
||||
Items are tuples: (object, (deps,))
|
||||
@ivar raw: The raw (unsorted) items.
|
||||
@type raw: list
|
||||
@ivar index: The index of (unsorted) items.
|
||||
@type index: list
|
||||
@ivar stack: The sorting stack.
|
||||
@type stack: list
|
||||
@ivar pushed: The I{pushed} set tracks items that have been
|
||||
processed.
|
||||
@type pushed: set
|
||||
@ivar sorted: The sorted list of items.
|
||||
@type sorted: list
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
""" """
|
||||
self.unsorted = []
|
||||
self.index = {}
|
||||
self.stack = []
|
||||
self.pushed = set()
|
||||
self.sorted = None
|
||||
|
||||
def add(self, *items):
|
||||
"""
|
||||
Add items to be sorted.
|
||||
@param items: One or more items to be added.
|
||||
@type items: I{item}
|
||||
@return: self
|
||||
@rtype: L{DepList}
|
||||
"""
|
||||
for item in items:
|
||||
self.unsorted.append(item)
|
||||
key = item[0]
|
||||
self.index[key] = item
|
||||
return self
|
||||
|
||||
def sort(self):
|
||||
"""
|
||||
Sort the list based on dependancies.
|
||||
@return: The sorted items.
|
||||
@rtype: list
|
||||
"""
|
||||
self.sorted = list()
|
||||
self.pushed = set()
|
||||
for item in self.unsorted:
|
||||
popped = []
|
||||
self.push(item)
|
||||
while len(self.stack):
|
||||
try:
|
||||
top = self.top()
|
||||
ref = top[1].next()
|
||||
refd = self.index.get(ref)
|
||||
if refd is None:
|
||||
log.debug('"%s" not found, skipped', Repr(ref))
|
||||
continue
|
||||
self.push(refd)
|
||||
except StopIteration:
|
||||
popped.append(self.pop())
|
||||
continue
|
||||
for p in popped:
|
||||
self.sorted.append(p)
|
||||
self.unsorted = self.sorted
|
||||
return self.sorted
|
||||
|
||||
def top(self):
|
||||
"""
|
||||
Get the item at the top of the stack.
|
||||
@return: The top item.
|
||||
@rtype: (item, iter)
|
||||
"""
|
||||
return self.stack[-1]
|
||||
|
||||
def push(self, item):
|
||||
"""
|
||||
Push and item onto the sorting stack.
|
||||
@param item: An item to push.
|
||||
@type item: I{item}
|
||||
@return: The number of items pushed.
|
||||
@rtype: int
|
||||
"""
|
||||
if item in self.pushed:
|
||||
return
|
||||
frame = (item, iter(item[1]))
|
||||
self.stack.append(frame)
|
||||
self.pushed.add(item)
|
||||
|
||||
def pop(self):
|
||||
"""
|
||||
Pop the top item off the stack and append
|
||||
it to the sorted list.
|
||||
@return: The popped item.
|
||||
@rtype: I{item}
|
||||
"""
|
||||
try:
|
||||
frame = self.stack.pop()
|
||||
return frame[0]
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
a = ('a', ('x',))
|
||||
b = ('b', ('a',))
|
||||
c = ('c', ('a','b'))
|
||||
d = ('d', ('c',))
|
||||
e = ('e', ('d','a'))
|
||||
f = ('f', ('e','c','d','a'))
|
||||
x = ('x', ())
|
||||
L = DepList()
|
||||
L.add(c, e, d, b, f, a, x)
|
||||
print [x[0] for x in L.sort()]
|
||||
@@ -0,0 +1,226 @@
|
||||
# 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 )
|
||||
|
||||
"""
|
||||
The I{doctor} module provides classes for fixing broken (sick)
|
||||
schema(s).
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
from suds.sax import splitPrefix, Namespace
|
||||
from suds.sax.element import Element
|
||||
from suds.plugin import DocumentPlugin, DocumentContext
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Doctor:
|
||||
"""
|
||||
Schema Doctor.
|
||||
"""
|
||||
def examine(self, root):
|
||||
"""
|
||||
Examine and repair the schema (if necessary).
|
||||
@param root: A schema root element.
|
||||
@type root: L{Element}
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Practice(Doctor):
|
||||
"""
|
||||
A collection of doctors.
|
||||
@ivar doctors: A list of doctors.
|
||||
@type doctors: list
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.doctors = []
|
||||
|
||||
def add(self, doctor):
|
||||
"""
|
||||
Add a doctor to the practice
|
||||
@param doctor: A doctor to add.
|
||||
@type doctor: L{Doctor}
|
||||
"""
|
||||
self.doctors.append(doctor)
|
||||
|
||||
def examine(self, root):
|
||||
for d in self.doctors:
|
||||
d.examine(root)
|
||||
return root
|
||||
|
||||
|
||||
class TnsFilter:
|
||||
"""
|
||||
Target Namespace filter.
|
||||
@ivar tns: A list of target namespaces.
|
||||
@type tns: [str,...]
|
||||
"""
|
||||
|
||||
def __init__(self, *tns):
|
||||
"""
|
||||
@param tns: A list of target namespaces.
|
||||
@type tns: [str,...]
|
||||
"""
|
||||
self.tns = []
|
||||
self.add(*tns)
|
||||
|
||||
def add(self, *tns):
|
||||
"""
|
||||
Add I{targetNamesapces} to be added.
|
||||
@param tns: A list of target namespaces.
|
||||
@type tns: [str,...]
|
||||
"""
|
||||
self.tns += tns
|
||||
|
||||
def match(self, root, ns):
|
||||
"""
|
||||
Match by I{targetNamespace} excluding those that
|
||||
are equal to the specified namespace to prevent
|
||||
adding an import to itself.
|
||||
@param root: A schema root.
|
||||
@type root: L{Element}
|
||||
"""
|
||||
tns = root.get('targetNamespace')
|
||||
if len(self.tns):
|
||||
matched = ( tns in self.tns )
|
||||
else:
|
||||
matched = 1
|
||||
itself = ( ns == tns )
|
||||
return ( matched and not itself )
|
||||
|
||||
|
||||
class Import:
|
||||
"""
|
||||
An <xs:import/> to be applied.
|
||||
@cvar xsdns: The XSD namespace.
|
||||
@type xsdns: (p,u)
|
||||
@ivar ns: An import namespace.
|
||||
@type ns: str
|
||||
@ivar location: An optional I{schemaLocation}.
|
||||
@type location: str
|
||||
@ivar filter: A filter used to restrict application to
|
||||
a particular schema.
|
||||
@type filter: L{TnsFilter}
|
||||
"""
|
||||
|
||||
xsdns = Namespace.xsdns
|
||||
|
||||
def __init__(self, ns, location=None):
|
||||
"""
|
||||
@param ns: An import namespace.
|
||||
@type ns: str
|
||||
@param location: An optional I{schemaLocation}.
|
||||
@type location: str
|
||||
"""
|
||||
self.ns = ns
|
||||
self.location = location
|
||||
self.filter = TnsFilter()
|
||||
|
||||
def setfilter(self, filter):
|
||||
"""
|
||||
Set the filter.
|
||||
@param filter: A filter to set.
|
||||
@type filter: L{TnsFilter}
|
||||
"""
|
||||
self.filter = filter
|
||||
|
||||
def apply(self, root):
|
||||
"""
|
||||
Apply the import (rule) to the specified schema.
|
||||
If the schema does not already contain an import for the
|
||||
I{namespace} specified here, it is added.
|
||||
@param root: A schema root.
|
||||
@type root: L{Element}
|
||||
"""
|
||||
if not self.filter.match(root, self.ns):
|
||||
return
|
||||
if self.exists(root):
|
||||
return
|
||||
node = Element('import', ns=self.xsdns)
|
||||
node.set('namespace', self.ns)
|
||||
if self.location is not None:
|
||||
node.set('schemaLocation', self.location)
|
||||
log.debug('inserting: %s', node)
|
||||
root.insert(node)
|
||||
|
||||
def add(self, root):
|
||||
"""
|
||||
Add an <xs:import/> to the specified schema root.
|
||||
@param root: A schema root.
|
||||
@type root: L{Element}
|
||||
"""
|
||||
node = Element('import', ns=self.xsdns)
|
||||
node.set('namespace', self.ns)
|
||||
if self.location is not None:
|
||||
node.set('schemaLocation', self.location)
|
||||
log.debug('%s inserted', node)
|
||||
root.insert(node)
|
||||
|
||||
def exists(self, root):
|
||||
"""
|
||||
Check to see if the <xs:import/> already exists
|
||||
in the specified schema root by matching I{namesapce}.
|
||||
@param root: A schema root.
|
||||
@type root: L{Element}
|
||||
"""
|
||||
for node in root.children:
|
||||
if node.name != 'import':
|
||||
continue
|
||||
ns = node.get('namespace')
|
||||
if self.ns == ns:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
class ImportDoctor(Doctor, DocumentPlugin):
|
||||
"""
|
||||
Doctor used to fix missing imports.
|
||||
@ivar imports: A list of imports to apply.
|
||||
@type imports: [L{Import},...]
|
||||
"""
|
||||
|
||||
def __init__(self, *imports):
|
||||
"""
|
||||
"""
|
||||
self.imports = []
|
||||
self.add(*imports)
|
||||
|
||||
def add(self, *imports):
|
||||
"""
|
||||
Add a namesapce to be checked.
|
||||
@param imports: A list of L{Import} objects.
|
||||
@type imports: [L{Import},..]
|
||||
"""
|
||||
self.imports += imports
|
||||
|
||||
def examine(self, node):
|
||||
for imp in self.imports:
|
||||
imp.apply(node)
|
||||
|
||||
def parsed(self, context):
|
||||
node = context.document
|
||||
# xsd root
|
||||
if node.name == 'schema' and Namespace.xsd(node.namespace()):
|
||||
self.examine(node)
|
||||
return
|
||||
# look deeper
|
||||
context = DocumentContext()
|
||||
for child in node:
|
||||
context.document = child
|
||||
self.parsed(context)
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
# 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 )
|
||||
|
||||
"""
|
||||
The I{query} module defines a class for performing schema queries.
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
from suds import *
|
||||
from suds.sudsobject import *
|
||||
from suds.xsd import qualify, isqref
|
||||
from suds.xsd.sxbuiltin import Factory
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Query(Object):
|
||||
"""
|
||||
Schema query base class.
|
||||
"""
|
||||
|
||||
def __init__(self, ref=None):
|
||||
"""
|
||||
@param ref: The schema reference being queried.
|
||||
@type ref: qref
|
||||
"""
|
||||
Object.__init__(self)
|
||||
self.id = objid(self)
|
||||
self.ref = ref
|
||||
self.history = []
|
||||
self.resolved = False
|
||||
if not isqref(self.ref):
|
||||
raise Exception('%s, must be qref' % tostr(self.ref))
|
||||
|
||||
def execute(self, schema):
|
||||
"""
|
||||
Execute this query using the specified schema.
|
||||
@param schema: The schema associated with the query. The schema
|
||||
is used by the query to search for items.
|
||||
@type schema: L{schema.Schema}
|
||||
@return: The item matching the search criteria.
|
||||
@rtype: L{sxbase.SchemaObject}
|
||||
"""
|
||||
raise Exception, 'not-implemented by subclass'
|
||||
|
||||
def filter(self, result):
|
||||
"""
|
||||
Filter the specified result based on query criteria.
|
||||
@param result: A potential result.
|
||||
@type result: L{sxbase.SchemaObject}
|
||||
@return: True if result should be excluded.
|
||||
@rtype: boolean
|
||||
"""
|
||||
if result is None:
|
||||
return True
|
||||
reject = ( result in self.history )
|
||||
if reject:
|
||||
log.debug('result %s, rejected by\n%s', Repr(result), self)
|
||||
return reject
|
||||
|
||||
def result(self, result):
|
||||
"""
|
||||
Query result post processing.
|
||||
@param result: A query result.
|
||||
@type result: L{sxbase.SchemaObject}
|
||||
"""
|
||||
if result is None:
|
||||
log.debug('%s, not-found', self.ref)
|
||||
return
|
||||
if self.resolved:
|
||||
result = result.resolve()
|
||||
log.debug('%s, found as: %s', self.ref, Repr(result))
|
||||
self.history.append(result)
|
||||
return result
|
||||
|
||||
|
||||
class BlindQuery(Query):
|
||||
"""
|
||||
Schema query class that I{blindly} searches for a reference in
|
||||
the specified schema. It may be used to find Elements and Types but
|
||||
will match on an Element first. This query will also find builtins.
|
||||
"""
|
||||
|
||||
def execute(self, schema):
|
||||
if schema.builtin(self.ref):
|
||||
name = self.ref[0]
|
||||
b = Factory.create(schema, name)
|
||||
log.debug('%s, found builtin (%s)', self.id, name)
|
||||
return b
|
||||
result = None
|
||||
for d in (schema.elements, schema.types):
|
||||
result = d.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = None
|
||||
else:
|
||||
break
|
||||
if result is None:
|
||||
eq = ElementQuery(self.ref)
|
||||
eq.history = self.history
|
||||
result = eq.execute(schema)
|
||||
return self.result(result)
|
||||
|
||||
|
||||
class TypeQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for Type references in
|
||||
the specified schema. Matches on root types only.
|
||||
"""
|
||||
|
||||
def execute(self, schema):
|
||||
if schema.builtin(self.ref):
|
||||
name = self.ref[0]
|
||||
b = Factory.create(schema, name)
|
||||
log.debug('%s, found builtin (%s)', self.id, name)
|
||||
return b
|
||||
result = schema.types.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = None
|
||||
return self.result(result)
|
||||
|
||||
|
||||
class GroupQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for Group references in
|
||||
the specified schema.
|
||||
"""
|
||||
|
||||
def execute(self, schema):
|
||||
result = schema.groups.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = None
|
||||
return self.result(result)
|
||||
|
||||
|
||||
class AttrQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for Attribute references in
|
||||
the specified schema. Matches on root Attribute by qname first, then searches
|
||||
deep into the document.
|
||||
"""
|
||||
|
||||
def execute(self, schema):
|
||||
result = schema.attributes.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = self.__deepsearch(schema)
|
||||
return self.result(result)
|
||||
|
||||
def __deepsearch(self, schema):
|
||||
from suds.xsd.sxbasic import Attribute
|
||||
result = None
|
||||
for e in schema.all:
|
||||
result = e.find(self.ref, (Attribute,))
|
||||
if self.filter(result):
|
||||
result = None
|
||||
else:
|
||||
break
|
||||
return result
|
||||
|
||||
|
||||
class AttrGroupQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for attributeGroup references in
|
||||
the specified schema.
|
||||
"""
|
||||
|
||||
def execute(self, schema):
|
||||
result = schema.agrps.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = None
|
||||
return self.result(result)
|
||||
|
||||
|
||||
class ElementQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for Element references in
|
||||
the specified schema. Matches on root Elements by qname first, then searches
|
||||
deep into the document.
|
||||
"""
|
||||
|
||||
def execute(self, schema):
|
||||
result = schema.elements.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = self.__deepsearch(schema)
|
||||
return self.result(result)
|
||||
|
||||
def __deepsearch(self, schema):
|
||||
from suds.xsd.sxbasic import Element
|
||||
result = None
|
||||
for e in schema.all:
|
||||
result = e.find(self.ref, (Element,))
|
||||
if self.filter(result):
|
||||
result = None
|
||||
else:
|
||||
break
|
||||
return result
|
||||
@@ -0,0 +1,422 @@
|
||||
# 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 )
|
||||
|
||||
"""
|
||||
The I{schema} module provides a intelligent representation of
|
||||
an XSD schema. The I{raw} model is the XML tree and the I{model}
|
||||
is the denormalized, objectified and intelligent view of the schema.
|
||||
Most of the I{value-add} provided by the model is centered around
|
||||
tranparent referenced type resolution and targeted denormalization.
|
||||
"""
|
||||
|
||||
|
||||
import suds.metrics
|
||||
from suds import *
|
||||
from suds.xsd import *
|
||||
from suds.xsd.sxbuiltin import *
|
||||
from suds.xsd.sxbasic import Factory as BasicFactory
|
||||
from suds.xsd.sxbuiltin import Factory as BuiltinFactory
|
||||
from suds.xsd.sxbase import SchemaObject
|
||||
from suds.xsd.deplist import DepList
|
||||
from suds.sax.element import Element
|
||||
from suds.sax import splitPrefix, Namespace
|
||||
from logging import getLogger
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class SchemaCollection:
|
||||
"""
|
||||
A collection of schema objects. This class is needed because WSDLs
|
||||
may contain more then one <schema/> node.
|
||||
@ivar wsdl: A wsdl object.
|
||||
@type wsdl: L{suds.wsdl.Definitions}
|
||||
@ivar children: A list contained schemas.
|
||||
@type children: [L{Schema},...]
|
||||
@ivar namespaces: A dictionary of contained schemas by namespace.
|
||||
@type namespaces: {str:L{Schema}}
|
||||
"""
|
||||
|
||||
def __init__(self, wsdl):
|
||||
"""
|
||||
@param wsdl: A wsdl object.
|
||||
@type wsdl: L{suds.wsdl.Definitions}
|
||||
"""
|
||||
self.wsdl = wsdl
|
||||
self.children = []
|
||||
self.namespaces = {}
|
||||
|
||||
def add(self, schema):
|
||||
"""
|
||||
Add a schema node to the collection. Schema(s) within the same target
|
||||
namespace are consolidated.
|
||||
@param schema: A schema object.
|
||||
@type schema: (L{Schema})
|
||||
"""
|
||||
key = schema.tns[1]
|
||||
existing = self.namespaces.get(key)
|
||||
if existing is None:
|
||||
self.children.append(schema)
|
||||
self.namespaces[key] = schema
|
||||
else:
|
||||
existing.root.children += schema.root.children
|
||||
existing.root.nsprefixes.update(schema.root.nsprefixes)
|
||||
|
||||
def load(self, options):
|
||||
"""
|
||||
Load the schema objects for the root nodes.
|
||||
- de-references schemas
|
||||
- merge schemas
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
@return: The merged schema.
|
||||
@rtype: L{Schema}
|
||||
"""
|
||||
if options.autoblend:
|
||||
self.autoblend()
|
||||
for child in self.children:
|
||||
child.build()
|
||||
for child in self.children:
|
||||
child.open_imports(options)
|
||||
for child in self.children:
|
||||
child.dereference()
|
||||
log.debug('loaded:\n%s', self)
|
||||
merged = self.merge()
|
||||
log.debug('MERGED:\n%s', merged)
|
||||
return merged
|
||||
|
||||
def autoblend(self):
|
||||
"""
|
||||
Ensure that all schemas within the collection
|
||||
import each other which has a blending effect.
|
||||
@return: self
|
||||
@rtype: L{SchemaCollection}
|
||||
"""
|
||||
namespaces = self.namespaces.keys()
|
||||
for s in self.children:
|
||||
for ns in namespaces:
|
||||
tns = s.root.get('targetNamespace')
|
||||
if tns == ns:
|
||||
continue
|
||||
for imp in s.root.getChildren('import'):
|
||||
if imp.get('namespace') == ns:
|
||||
continue
|
||||
imp = Element('import', ns=Namespace.xsdns)
|
||||
imp.set('namespace', ns)
|
||||
s.root.append(imp)
|
||||
return self
|
||||
|
||||
def locate(self, ns):
|
||||
"""
|
||||
Find a schema by namespace. Only the URI portion of
|
||||
the namespace is compared to each schema's I{targetNamespace}
|
||||
@param ns: A namespace.
|
||||
@type ns: (prefix,URI)
|
||||
@return: The schema matching the namesapce, else None.
|
||||
@rtype: L{Schema}
|
||||
"""
|
||||
return self.namespaces.get(ns[1])
|
||||
|
||||
def merge(self):
|
||||
"""
|
||||
Merge the contained schemas into one.
|
||||
@return: The merged schema.
|
||||
@rtype: L{Schema}
|
||||
"""
|
||||
if len(self):
|
||||
schema = self.children[0]
|
||||
for s in self.children[1:]:
|
||||
schema.merge(s)
|
||||
return schema
|
||||
else:
|
||||
return None
|
||||
|
||||
def __len__(self):
|
||||
return len(self.children)
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self).encode('utf-8')
|
||||
|
||||
def __unicode__(self):
|
||||
result = ['\nschema collection']
|
||||
for s in self.children:
|
||||
result.append(s.str(1))
|
||||
return '\n'.join(result)
|
||||
|
||||
|
||||
class Schema:
|
||||
"""
|
||||
The schema is an objectification of a <schema/> (xsd) definition.
|
||||
It provides inspection, lookup and type resolution.
|
||||
@ivar root: The root node.
|
||||
@type root: L{sax.element.Element}
|
||||
@ivar baseurl: The I{base} URL for this schema.
|
||||
@type baseurl: str
|
||||
@ivar container: A schema collection containing this schema.
|
||||
@type container: L{SchemaCollection}
|
||||
@ivar children: A list of direct top level children.
|
||||
@type children: [L{SchemaObject},...]
|
||||
@ivar all: A list of all (includes imported) top level children.
|
||||
@type all: [L{SchemaObject},...]
|
||||
@ivar types: A schema types cache.
|
||||
@type types: {name:L{SchemaObject}}
|
||||
@ivar imports: A list of import objects.
|
||||
@type imports: [L{SchemaObject},...]
|
||||
@ivar elements: A list of <element/> objects.
|
||||
@type elements: [L{SchemaObject},...]
|
||||
@ivar attributes: A list of <attribute/> objects.
|
||||
@type attributes: [L{SchemaObject},...]
|
||||
@ivar groups: A list of group objects.
|
||||
@type groups: [L{SchemaObject},...]
|
||||
@ivar agrps: A list of attribute group objects.
|
||||
@type agrps: [L{SchemaObject},...]
|
||||
@ivar form_qualified: The flag indicating:
|
||||
(@elementFormDefault).
|
||||
@type form_qualified: bool
|
||||
"""
|
||||
|
||||
Tag = 'schema'
|
||||
|
||||
def __init__(self, root, baseurl, options, container=None):
|
||||
"""
|
||||
@param root: The xml root.
|
||||
@type root: L{sax.element.Element}
|
||||
@param baseurl: The base url used for importing.
|
||||
@type baseurl: basestring
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
@param container: An optional container.
|
||||
@type container: L{SchemaCollection}
|
||||
"""
|
||||
self.root = root
|
||||
self.id = objid(self)
|
||||
self.tns = self.mktns()
|
||||
self.baseurl = baseurl
|
||||
self.container = container
|
||||
self.children = []
|
||||
self.all = []
|
||||
self.types = {}
|
||||
self.imports = []
|
||||
self.elements = {}
|
||||
self.attributes = {}
|
||||
self.groups = {}
|
||||
self.agrps = {}
|
||||
if options.doctor is not None:
|
||||
options.doctor.examine(root)
|
||||
form = self.root.get('elementFormDefault')
|
||||
if form is None:
|
||||
self.form_qualified = False
|
||||
else:
|
||||
self.form_qualified = ( form == 'qualified' )
|
||||
if container is None:
|
||||
self.build()
|
||||
self.open_imports(options)
|
||||
log.debug('built:\n%s', self)
|
||||
self.dereference()
|
||||
log.debug('dereferenced:\n%s', self)
|
||||
|
||||
def mktns(self):
|
||||
"""
|
||||
Make the schema's target namespace.
|
||||
@return: The namespace representation of the schema's
|
||||
targetNamespace value.
|
||||
@rtype: (prefix, uri)
|
||||
"""
|
||||
tns = [None, self.root.get('targetNamespace')]
|
||||
if tns[1] is not None:
|
||||
tns[0] = self.root.findPrefix(tns[1])
|
||||
return tuple(tns)
|
||||
|
||||
def build(self):
|
||||
"""
|
||||
Build the schema (object graph) using the root node
|
||||
using the factory.
|
||||
- Build the graph.
|
||||
- Collate the children.
|
||||
"""
|
||||
self.children = BasicFactory.build(self.root, self)
|
||||
collated = BasicFactory.collate(self.children)
|
||||
self.children = collated[0]
|
||||
self.attributes = collated[2]
|
||||
self.imports = collated[1]
|
||||
self.elements = collated[3]
|
||||
self.types = collated[4]
|
||||
self.groups = collated[5]
|
||||
self.agrps = collated[6]
|
||||
|
||||
def merge(self, schema):
|
||||
"""
|
||||
Merge the contents from the schema. Only objects not already contained
|
||||
in this schema's collections are merged. This is to provide for bidirectional
|
||||
import which produce cyclic includes.
|
||||
@returns: self
|
||||
@rtype: L{Schema}
|
||||
"""
|
||||
for item in schema.attributes.items():
|
||||
if item[0] in self.attributes:
|
||||
continue
|
||||
self.all.append(item[1])
|
||||
self.attributes[item[0]] = item[1]
|
||||
for item in schema.elements.items():
|
||||
if item[0] in self.elements:
|
||||
continue
|
||||
self.all.append(item[1])
|
||||
self.elements[item[0]] = item[1]
|
||||
for item in schema.types.items():
|
||||
if item[0] in self.types:
|
||||
continue
|
||||
self.all.append(item[1])
|
||||
self.types[item[0]] = item[1]
|
||||
for item in schema.groups.items():
|
||||
if item[0] in self.groups:
|
||||
continue
|
||||
self.all.append(item[1])
|
||||
self.groups[item[0]] = item[1]
|
||||
for item in schema.agrps.items():
|
||||
if item[0] in self.agrps:
|
||||
continue
|
||||
self.all.append(item[1])
|
||||
self.agrps[item[0]] = item[1]
|
||||
schema.merged = True
|
||||
return self
|
||||
|
||||
def open_imports(self, options):
|
||||
"""
|
||||
Instruct all contained L{sxbasic.Import} children to import
|
||||
the schema's which they reference. The contents of the
|
||||
imported schema are I{merged} in.
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
"""
|
||||
for imp in self.imports:
|
||||
imported = imp.open(options)
|
||||
if imported is None:
|
||||
continue
|
||||
imported.open_imports(options)
|
||||
log.debug('imported:\n%s', imported)
|
||||
self.merge(imported)
|
||||
|
||||
def dereference(self):
|
||||
"""
|
||||
Instruct all children to perform dereferencing.
|
||||
"""
|
||||
all = []
|
||||
indexes = {}
|
||||
for child in self.children:
|
||||
child.content(all)
|
||||
deplist = DepList()
|
||||
for x in all:
|
||||
x.qualify()
|
||||
midx, deps = x.dependencies()
|
||||
item = (x, tuple(deps))
|
||||
deplist.add(item)
|
||||
indexes[x] = midx
|
||||
for x, deps in deplist.sort():
|
||||
midx = indexes.get(x)
|
||||
if midx is None: continue
|
||||
d = deps[midx]
|
||||
log.debug('(%s) merging %s <== %s', self.tns[1], Repr(x), Repr(d))
|
||||
x.merge(d)
|
||||
|
||||
def locate(self, ns):
|
||||
"""
|
||||
Find a schema by namespace. Only the URI portion of
|
||||
the namespace is compared to each schema's I{targetNamespace}.
|
||||
The request is passed to the container.
|
||||
@param ns: A namespace.
|
||||
@type ns: (prefix,URI)
|
||||
@return: The schema matching the namesapce, else None.
|
||||
@rtype: L{Schema}
|
||||
"""
|
||||
if self.container is not None:
|
||||
return self.container.locate(ns)
|
||||
else:
|
||||
return None
|
||||
|
||||
def custom(self, ref, context=None):
|
||||
"""
|
||||
Get whether the specified reference is B{not} an (xs) builtin.
|
||||
@param ref: A str or qref.
|
||||
@type ref: (str|qref)
|
||||
@return: True if B{not} a builtin, else False.
|
||||
@rtype: bool
|
||||
"""
|
||||
if ref is None:
|
||||
return True
|
||||
else:
|
||||
return ( not self.builtin(ref, context) )
|
||||
|
||||
def builtin(self, ref, context=None):
|
||||
"""
|
||||
Get whether the specified reference is an (xs) builtin.
|
||||
@param ref: A str or qref.
|
||||
@type ref: (str|qref)
|
||||
@return: True if builtin, else False.
|
||||
@rtype: bool
|
||||
"""
|
||||
w3 = 'http://www.w3.org'
|
||||
try:
|
||||
if isqref(ref):
|
||||
ns = ref[1]
|
||||
return ( ref[0] in Factory.tags and ns.startswith(w3) )
|
||||
if context is None:
|
||||
context = self.root
|
||||
prefix = splitPrefix(ref)[0]
|
||||
prefixes = context.findPrefixes(w3, 'startswith')
|
||||
return ( prefix in prefixes and ref[0] in Factory.tags )
|
||||
except:
|
||||
return False
|
||||
|
||||
def instance(self, root, baseurl, options):
|
||||
"""
|
||||
Create and return an new schema object using the
|
||||
specified I{root} and I{url}.
|
||||
@param root: A schema root node.
|
||||
@type root: L{sax.element.Element}
|
||||
@param baseurl: A base URL.
|
||||
@type baseurl: str
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
@return: The newly created schema object.
|
||||
@rtype: L{Schema}
|
||||
@note: This is only used by Import children.
|
||||
"""
|
||||
return Schema(root, baseurl, options)
|
||||
|
||||
def str(self, indent=0):
|
||||
tab = '%*s'%(indent*3, '')
|
||||
result = []
|
||||
result.append('%s%s' % (tab, self.id))
|
||||
result.append('%s(raw)' % tab)
|
||||
result.append(self.root.str(indent+1))
|
||||
result.append('%s(model)' % tab)
|
||||
for c in self.children:
|
||||
result.append(c.str(indent+1))
|
||||
result.append('')
|
||||
return '\n'.join(result)
|
||||
|
||||
def __repr__(self):
|
||||
myrep = '<%s tns="%s"/>' % (self.id, self.tns[1])
|
||||
return myrep.encode('utf-8')
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self).encode('utf-8')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.str()
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,669 @@
|
||||
# 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 )
|
||||
|
||||
"""
|
||||
The I{sxbase} module provides I{base} classes that represent
|
||||
schema objects.
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
from suds import *
|
||||
from suds.xsd import *
|
||||
from suds.sax.element import Element
|
||||
from suds.sax import Namespace
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class SchemaObject(object):
|
||||
"""
|
||||
A schema object is an extension to object object with
|
||||
with schema awareness.
|
||||
@ivar root: The XML root element.
|
||||
@type root: L{Element}
|
||||
@ivar schema: The schema containing this object.
|
||||
@type schema: L{schema.Schema}
|
||||
@ivar form_qualified: A flag that inidcates that @elementFormDefault
|
||||
has a value of I{qualified}.
|
||||
@type form_qualified: boolean
|
||||
@ivar nillable: A flag that inidcates that @nillable
|
||||
has a value of I{true}.
|
||||
@type nillable: boolean
|
||||
@ivar default: The default value.
|
||||
@type default: object
|
||||
@ivar rawchildren: A list raw of all children.
|
||||
@type rawchildren: [L{SchemaObject},...]
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def prepend(cls, d, s, filter=Filter()):
|
||||
"""
|
||||
Prepend schema object's from B{s}ource list to
|
||||
the B{d}estination list while applying the filter.
|
||||
@param d: The destination list.
|
||||
@type d: list
|
||||
@param s: The source list.
|
||||
@type s: list
|
||||
@param filter: A filter that allows items to be prepended.
|
||||
@type filter: L{Filter}
|
||||
"""
|
||||
i = 0
|
||||
for x in s:
|
||||
if x in filter:
|
||||
d.insert(i, x)
|
||||
i += 1
|
||||
|
||||
@classmethod
|
||||
def append(cls, d, s, filter=Filter()):
|
||||
"""
|
||||
Append schema object's from B{s}ource list to
|
||||
the B{d}estination list while applying the filter.
|
||||
@param d: The destination list.
|
||||
@type d: list
|
||||
@param s: The source list.
|
||||
@type s: list
|
||||
@param filter: A filter that allows items to be appended.
|
||||
@type filter: L{Filter}
|
||||
"""
|
||||
for item in s:
|
||||
if item in filter:
|
||||
d.append(item)
|
||||
|
||||
def __init__(self, schema, root):
|
||||
"""
|
||||
@param schema: The containing schema.
|
||||
@type schema: L{schema.Schema}
|
||||
@param root: The xml root node.
|
||||
@type root: L{Element}
|
||||
"""
|
||||
self.schema = schema
|
||||
self.root = root
|
||||
self.id = objid(self)
|
||||
self.name = root.get('name')
|
||||
self.qname = (self.name, schema.tns[1])
|
||||
self.min = root.get('minOccurs')
|
||||
self.max = root.get('maxOccurs')
|
||||
self.type = root.get('type')
|
||||
self.ref = root.get('ref')
|
||||
self.form_qualified = schema.form_qualified
|
||||
self.nillable = False
|
||||
self.default = root.get('default')
|
||||
self.rawchildren = []
|
||||
self.cache = {}
|
||||
|
||||
def attributes(self, filter=Filter()):
|
||||
"""
|
||||
Get only the attribute content.
|
||||
@param filter: A filter to constrain the result.
|
||||
@type filter: L{Filter}
|
||||
@return: A list of tuples (attr, ancestry)
|
||||
@rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..]
|
||||
"""
|
||||
result = []
|
||||
for child, ancestry in self:
|
||||
if child.isattr() and child in filter:
|
||||
result.append((child, ancestry))
|
||||
return result
|
||||
|
||||
def children(self, filter=Filter()):
|
||||
"""
|
||||
Get only the I{direct} or non-attribute content.
|
||||
@param filter: A filter to constrain the result.
|
||||
@type filter: L{Filter}
|
||||
@return: A list tuples: (child, ancestry)
|
||||
@rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..]
|
||||
"""
|
||||
result = []
|
||||
for child, ancestry in self:
|
||||
if not child.isattr() and child in filter:
|
||||
result.append((child, ancestry))
|
||||
return result
|
||||
|
||||
def get_attribute(self, name):
|
||||
"""
|
||||
Get (find) a I{non-attribute} attribute by name.
|
||||
@param name: A attribute name.
|
||||
@type name: str
|
||||
@return: A tuple: the requested (attribute, ancestry).
|
||||
@rtype: (L{SchemaObject}, [L{SchemaObject},..])
|
||||
"""
|
||||
for child, ancestry in self.attributes():
|
||||
if child.name == name:
|
||||
return (child, ancestry)
|
||||
return (None, [])
|
||||
|
||||
def get_child(self, name):
|
||||
"""
|
||||
Get (find) a I{non-attribute} child by name.
|
||||
@param name: A child name.
|
||||
@type name: str
|
||||
@return: A tuple: the requested (child, ancestry).
|
||||
@rtype: (L{SchemaObject}, [L{SchemaObject},..])
|
||||
"""
|
||||
for child, ancestry in self.children():
|
||||
if child.any() or child.name == name:
|
||||
return (child, ancestry)
|
||||
return (None, [])
|
||||
|
||||
def namespace(self, prefix=None):
|
||||
"""
|
||||
Get this properties namespace
|
||||
@param prefix: The default prefix.
|
||||
@type prefix: str
|
||||
@return: The schema's target namespace
|
||||
@rtype: (I{prefix},I{URI})
|
||||
"""
|
||||
ns = self.schema.tns
|
||||
if ns[0] is None:
|
||||
ns = (prefix, ns[1])
|
||||
return ns
|
||||
|
||||
def default_namespace(self):
|
||||
return self.root.defaultNamespace()
|
||||
|
||||
def unbounded(self):
|
||||
"""
|
||||
Get whether this node is unbounded I{(a collection)}
|
||||
@return: True if unbounded, else False.
|
||||
@rtype: boolean
|
||||
"""
|
||||
max = self.max
|
||||
if max is None:
|
||||
max = '1'
|
||||
if max.isdigit():
|
||||
return (int(max) > 1)
|
||||
else:
|
||||
return ( max == 'unbounded' )
|
||||
|
||||
def optional(self):
|
||||
"""
|
||||
Get whether this type is optional.
|
||||
@return: True if optional, else False
|
||||
@rtype: boolean
|
||||
"""
|
||||
min = self.min
|
||||
if min is None:
|
||||
min = '1'
|
||||
return ( min == '0' )
|
||||
|
||||
def required(self):
|
||||
"""
|
||||
Get whether this type is required.
|
||||
@return: True if required, else False
|
||||
@rtype: boolean
|
||||
"""
|
||||
return ( not self.optional() )
|
||||
|
||||
|
||||
def resolve(self, nobuiltin=False):
|
||||
"""
|
||||
Resolve and return the nodes true self.
|
||||
@param nobuiltin: Flag indicates that resolution must
|
||||
not continue to include xsd builtins.
|
||||
@return: The resolved (true) type.
|
||||
@rtype: L{SchemaObject}
|
||||
"""
|
||||
return self.cache.get(nobuiltin, self)
|
||||
|
||||
def sequence(self):
|
||||
"""
|
||||
Get whether this is an <xs:sequence/>
|
||||
@return: True if <xs:sequence/>, else False
|
||||
@rtype: boolean
|
||||
"""
|
||||
return False
|
||||
|
||||
def xslist(self):
|
||||
"""
|
||||
Get whether this is an <xs:list/>
|
||||
@return: True if any, else False
|
||||
@rtype: boolean
|
||||
"""
|
||||
return False
|
||||
|
||||
def all(self):
|
||||
"""
|
||||
Get whether this is an <xs:all/>
|
||||
@return: True if any, else False
|
||||
@rtype: boolean
|
||||
"""
|
||||
return False
|
||||
|
||||
def choice(self):
|
||||
"""
|
||||
Get whether this is n <xs:choice/>
|
||||
@return: True if any, else False
|
||||
@rtype: boolean
|
||||
"""
|
||||
return False
|
||||
|
||||
def any(self):
|
||||
"""
|
||||
Get whether this is an <xs:any/>
|
||||
@return: True if any, else False
|
||||
@rtype: boolean
|
||||
"""
|
||||
return False
|
||||
|
||||
def builtin(self):
|
||||
"""
|
||||
Get whether this is a schema-instance (xs) type.
|
||||
@return: True if any, else False
|
||||
@rtype: boolean
|
||||
"""
|
||||
return False
|
||||
|
||||
def enum(self):
|
||||
"""
|
||||
Get whether this is a simple-type containing an enumeration.
|
||||
@return: True if any, else False
|
||||
@rtype: boolean
|
||||
"""
|
||||
return False
|
||||
|
||||
def isattr(self):
|
||||
"""
|
||||
Get whether the object is a schema I{attribute} definition.
|
||||
@return: True if an attribute, else False.
|
||||
@rtype: boolean
|
||||
"""
|
||||
return False
|
||||
|
||||
def extension(self):
|
||||
"""
|
||||
Get whether the object is an extension of another type.
|
||||
@return: True if an extension, else False.
|
||||
@rtype: boolean
|
||||
"""
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
"""
|
||||
Get whether the object is an restriction of another type.
|
||||
@return: True if an restriction, else False.
|
||||
@rtype: boolean
|
||||
"""
|
||||
return False
|
||||
|
||||
def mixed(self):
|
||||
"""
|
||||
Get whether this I{mixed} content.
|
||||
"""
|
||||
return False
|
||||
|
||||
def find(self, qref, classes=()):
|
||||
"""
|
||||
Find a referenced type in self or children.
|
||||
@param qref: A qualified reference.
|
||||
@type qref: qref
|
||||
@param classes: A list of classes used to qualify the match.
|
||||
@type classes: [I{class},...]
|
||||
@return: The referenced type.
|
||||
@rtype: L{SchemaObject}
|
||||
@see: L{qualify()}
|
||||
"""
|
||||
if not len(classes):
|
||||
classes = (self.__class__,)
|
||||
if self.qname == qref and self.__class__ in classes:
|
||||
return self
|
||||
for c in self.rawchildren:
|
||||
p = c.find(qref, classes)
|
||||
if p is not None:
|
||||
return p
|
||||
return None
|
||||
|
||||
def translate(self, value, topython=True):
|
||||
"""
|
||||
Translate a value (type) to/from a python type.
|
||||
@param value: A value to translate.
|
||||
@return: The converted I{language} type.
|
||||
"""
|
||||
return value
|
||||
|
||||
def childtags(self):
|
||||
"""
|
||||
Get a list of valid child tag names.
|
||||
@return: A list of child tag names.
|
||||
@rtype: [str,...]
|
||||
"""
|
||||
return ()
|
||||
|
||||
def dependencies(self):
|
||||
"""
|
||||
Get a list of dependancies for dereferencing.
|
||||
@return: A merge dependancy index and a list of dependancies.
|
||||
@rtype: (int, [L{SchemaObject},...])
|
||||
"""
|
||||
return (None, [])
|
||||
|
||||
def autoqualified(self):
|
||||
"""
|
||||
The list of I{auto} qualified attribute values.
|
||||
Qualification means to convert values into I{qref}.
|
||||
@return: A list of attibute names.
|
||||
@rtype: list
|
||||
"""
|
||||
return ['type', 'ref']
|
||||
|
||||
def qualify(self):
|
||||
"""
|
||||
Convert attribute values, that are references to other
|
||||
objects, into I{qref}. Qualfied using default document namespace.
|
||||
Since many wsdls are written improperly: when the document does
|
||||
not define a default namespace, the schema target namespace is used
|
||||
to qualify references.
|
||||
"""
|
||||
defns = self.root.defaultNamespace()
|
||||
if Namespace.none(defns):
|
||||
defns = self.schema.tns
|
||||
for a in self.autoqualified():
|
||||
ref = getattr(self, a)
|
||||
if ref is None:
|
||||
continue
|
||||
if isqref(ref):
|
||||
continue
|
||||
qref = qualify(ref, self.root, defns)
|
||||
log.debug('%s, convert %s="%s" to %s', self.id, a, ref, qref)
|
||||
setattr(self, a, qref)
|
||||
|
||||
def merge(self, other):
|
||||
"""
|
||||
Merge another object as needed.
|
||||
"""
|
||||
other.qualify()
|
||||
for n in ('name',
|
||||
'qname',
|
||||
'min',
|
||||
'max',
|
||||
'default',
|
||||
'type',
|
||||
'nillable',
|
||||
'form_qualified',):
|
||||
if getattr(self, n) is not None:
|
||||
continue
|
||||
v = getattr(other, n)
|
||||
if v is None:
|
||||
continue
|
||||
setattr(self, n, v)
|
||||
|
||||
|
||||
def content(self, collection=None, filter=Filter(), history=None):
|
||||
"""
|
||||
Get a I{flattened} list of this nodes contents.
|
||||
@param collection: A list to fill.
|
||||
@type collection: list
|
||||
@param filter: A filter used to constrain the result.
|
||||
@type filter: L{Filter}
|
||||
@param history: The history list used to prevent cyclic dependency.
|
||||
@type history: list
|
||||
@return: The filled list.
|
||||
@rtype: list
|
||||
"""
|
||||
if collection is None:
|
||||
collection = []
|
||||
if history is None:
|
||||
history = []
|
||||
if self in history:
|
||||
return collection
|
||||
history.append(self)
|
||||
if self in filter:
|
||||
collection.append(self)
|
||||
for c in self.rawchildren:
|
||||
c.content(collection, filter, history[:])
|
||||
return collection
|
||||
|
||||
def str(self, indent=0, history=None):
|
||||
"""
|
||||
Get a string representation of this object.
|
||||
@param indent: The indent.
|
||||
@type indent: int
|
||||
@return: A string.
|
||||
@rtype: str
|
||||
"""
|
||||
if history is None:
|
||||
history = []
|
||||
if self in history:
|
||||
return '%s ...' % Repr(self)
|
||||
history.append(self)
|
||||
tab = '%*s'%(indent*3, '')
|
||||
result = []
|
||||
result.append('%s<%s' % (tab, self.id))
|
||||
for n in self.description():
|
||||
if not hasattr(self, n):
|
||||
continue
|
||||
v = getattr(self, n)
|
||||
if v is None:
|
||||
continue
|
||||
result.append(' %s="%s"' % (n, v))
|
||||
if len(self):
|
||||
result.append('>')
|
||||
for c in self.rawchildren:
|
||||
result.append('\n')
|
||||
result.append(c.str(indent+1, history[:]))
|
||||
if c.isattr():
|
||||
result.append('@')
|
||||
result.append('\n%s' % tab)
|
||||
result.append('</%s>' % self.__class__.__name__)
|
||||
else:
|
||||
result.append(' />')
|
||||
return ''.join(result)
|
||||
|
||||
def description(self):
|
||||
"""
|
||||
Get the names used for str() and repr() description.
|
||||
@return: A dictionary of relavent attributes.
|
||||
@rtype: [str,...]
|
||||
"""
|
||||
return ()
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self).encode('utf-8')
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.str())
|
||||
|
||||
def __repr__(self):
|
||||
s = []
|
||||
s.append('<%s' % self.id)
|
||||
for n in self.description():
|
||||
if not hasattr(self, n):
|
||||
continue
|
||||
v = getattr(self, n)
|
||||
if v is None:
|
||||
continue
|
||||
s.append(' %s="%s"' % (n, v))
|
||||
s.append(' />')
|
||||
myrep = ''.join(s)
|
||||
return myrep.encode('utf-8')
|
||||
|
||||
def __len__(self):
|
||||
n = 0
|
||||
for x in self: n += 1
|
||||
return n
|
||||
|
||||
def __iter__(self):
|
||||
return Iter(self)
|
||||
|
||||
def __getitem__(self, index):
|
||||
i = 0
|
||||
for c in self:
|
||||
if i == index:
|
||||
return c
|
||||
|
||||
|
||||
class Iter:
|
||||
"""
|
||||
The content iterator - used to iterate the L{Content} children. The iterator
|
||||
provides a I{view} of the children that is free of container elements
|
||||
such as <sequence/> and <choice/>.
|
||||
@ivar stack: A stack used to control nesting.
|
||||
@type stack: list
|
||||
"""
|
||||
|
||||
class Frame:
|
||||
""" A content iterator frame. """
|
||||
|
||||
def __init__(self, sx):
|
||||
"""
|
||||
@param sx: A schema object.
|
||||
@type sx: L{SchemaObject}
|
||||
"""
|
||||
self.sx = sx
|
||||
self.items = sx.rawchildren
|
||||
self.index = 0
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
Get the I{next} item in the frame's collection.
|
||||
@return: The next item or None
|
||||
@rtype: L{SchemaObject}
|
||||
"""
|
||||
if self.index < len(self.items):
|
||||
result = self.items[self.index]
|
||||
self.index += 1
|
||||
return result
|
||||
|
||||
def __init__(self, sx):
|
||||
"""
|
||||
@param sx: A schema object.
|
||||
@type sx: L{SchemaObject}
|
||||
"""
|
||||
self.stack = []
|
||||
self.push(sx)
|
||||
|
||||
def push(self, sx):
|
||||
"""
|
||||
Create a frame and push the specified object.
|
||||
@param sx: A schema object to push.
|
||||
@type sx: L{SchemaObject}
|
||||
"""
|
||||
self.stack.append(Iter.Frame(sx))
|
||||
|
||||
def pop(self):
|
||||
"""
|
||||
Pop the I{top} frame.
|
||||
@return: The popped frame.
|
||||
@rtype: L{Frame}
|
||||
@raise StopIteration: when stack is empty.
|
||||
"""
|
||||
if len(self.stack):
|
||||
return self.stack.pop()
|
||||
else:
|
||||
raise StopIteration()
|
||||
|
||||
def top(self):
|
||||
"""
|
||||
Get the I{top} frame.
|
||||
@return: The top frame.
|
||||
@rtype: L{Frame}
|
||||
@raise StopIteration: when stack is empty.
|
||||
"""
|
||||
if len(self.stack):
|
||||
return self.stack[-1]
|
||||
else:
|
||||
raise StopIteration()
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
Get the next item.
|
||||
@return: A tuple: the next (child, ancestry).
|
||||
@rtype: (L{SchemaObject}, [L{SchemaObject},..])
|
||||
@raise StopIteration: A the end.
|
||||
"""
|
||||
frame = self.top()
|
||||
while True:
|
||||
result = frame.next()
|
||||
if result is None:
|
||||
self.pop()
|
||||
return self.next()
|
||||
if isinstance(result, Content):
|
||||
ancestry = [f.sx for f in self.stack]
|
||||
return (result, ancestry)
|
||||
self.push(result)
|
||||
return self.next()
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
|
||||
class XBuiltin(SchemaObject):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:*/> node
|
||||
"""
|
||||
|
||||
def __init__(self, schema, name):
|
||||
"""
|
||||
@param schema: The containing schema.
|
||||
@type schema: L{schema.Schema}
|
||||
"""
|
||||
root = Element(name)
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.name = name
|
||||
self.nillable = True
|
||||
|
||||
def namespace(self, prefix=None):
|
||||
return Namespace.xsdns
|
||||
|
||||
def builtin(self):
|
||||
return True
|
||||
|
||||
def resolve(self, nobuiltin=False):
|
||||
return self
|
||||
|
||||
|
||||
class Content(SchemaObject):
|
||||
"""
|
||||
This class represents those schema objects that represent
|
||||
real XML document content.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NodeFinder:
|
||||
"""
|
||||
Find nodes based on flexable criteria. The I{matcher} is
|
||||
may be any object that implements a match(n) method.
|
||||
@ivar matcher: An object used as criteria for match.
|
||||
@type matcher: I{any}.match(n)
|
||||
@ivar limit: Limit the number of matches. 0=unlimited.
|
||||
@type limit: int
|
||||
"""
|
||||
def __init__(self, matcher, limit=0):
|
||||
"""
|
||||
@param matcher: An object used as criteria for match.
|
||||
@type matcher: I{any}.match(n)
|
||||
@param limit: Limit the number of matches. 0=unlimited.
|
||||
@type limit: int
|
||||
"""
|
||||
self.matcher = matcher
|
||||
self.limit = limit
|
||||
|
||||
def find(self, node, list):
|
||||
"""
|
||||
Traverse the tree looking for matches.
|
||||
@param node: A node to match on.
|
||||
@type node: L{SchemaObject}
|
||||
@param list: A list to fill.
|
||||
@type list: list
|
||||
"""
|
||||
if self.matcher.match(node):
|
||||
list.append(node)
|
||||
self.limit -= 1
|
||||
if self.limit == 0:
|
||||
return
|
||||
for c in node.rawchildren:
|
||||
self.find(c, list)
|
||||
return self
|
||||
@@ -0,0 +1,825 @@
|
||||
# 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 )
|
||||
|
||||
"""
|
||||
The I{sxbasic} module provides classes that represent
|
||||
I{basic} schema objects.
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
from suds import *
|
||||
from suds.xsd import *
|
||||
from suds.xsd.sxbase import *
|
||||
from suds.xsd.query import *
|
||||
from suds.sax import splitPrefix, Namespace
|
||||
from suds.transport import TransportError
|
||||
from suds.reader import DocumentReader
|
||||
from urlparse import urljoin
|
||||
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class RestrictionMatcher:
|
||||
"""
|
||||
For use with L{NodeFinder} to match restriction.
|
||||
"""
|
||||
def match(self, n):
|
||||
return isinstance(n, Restriction)
|
||||
|
||||
|
||||
class TypedContent(Content):
|
||||
"""
|
||||
Represents any I{typed} content.
|
||||
"""
|
||||
def resolve(self, nobuiltin=False):
|
||||
qref = self.qref()
|
||||
if qref is None:
|
||||
return self
|
||||
key = 'resolved:nb=%s' % nobuiltin
|
||||
cached = self.cache.get(key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
result = self
|
||||
query = TypeQuery(qref)
|
||||
query.history = [self]
|
||||
log.debug('%s, resolving: %s\n using:%s', self.id, qref, query)
|
||||
resolved = query.execute(self.schema)
|
||||
if resolved is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(qref)
|
||||
self.cache[key] = resolved
|
||||
if resolved.builtin():
|
||||
if nobuiltin:
|
||||
result = self
|
||||
else:
|
||||
result = resolved
|
||||
else:
|
||||
result = resolved.resolve(nobuiltin)
|
||||
return result
|
||||
|
||||
def qref(self):
|
||||
"""
|
||||
Get the I{type} qualified reference to the referenced xsd type.
|
||||
This method takes into account simple types defined through
|
||||
restriction with are detected by determining that self is simple
|
||||
(len=0) and by finding a restriction child.
|
||||
@return: The I{type} qualified reference.
|
||||
@rtype: qref
|
||||
"""
|
||||
qref = self.type
|
||||
if qref is None and len(self) == 0:
|
||||
ls = []
|
||||
m = RestrictionMatcher()
|
||||
finder = NodeFinder(m, 1)
|
||||
finder.find(self, ls)
|
||||
if len(ls):
|
||||
return ls[0].ref
|
||||
return qref
|
||||
|
||||
|
||||
class Complex(SchemaObject):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:complexType/> node.
|
||||
@cvar childtags: A list of valid child node names
|
||||
@type childtags: (I{str},...)
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return (
|
||||
'attribute',
|
||||
'attributeGroup',
|
||||
'sequence',
|
||||
'all',
|
||||
'choice',
|
||||
'complexContent',
|
||||
'simpleContent',
|
||||
'any',
|
||||
'group')
|
||||
|
||||
def description(self):
|
||||
return ('name',)
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def mixed(self):
|
||||
for c in self.rawchildren:
|
||||
if isinstance(c, SimpleContent) and c.mixed():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Group(SchemaObject):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:group/> node.
|
||||
@cvar childtags: A list of valid child node names
|
||||
@type childtags: (I{str},...)
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return ('sequence', 'all', 'choice')
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = GroupQuery(self.ref)
|
||||
g = query.execute(self.schema)
|
||||
if g is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
deps.append(g)
|
||||
midx = 0
|
||||
return (midx, deps)
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
self.rawchildren = other.rawchildren
|
||||
|
||||
def description(self):
|
||||
return ('name', 'ref',)
|
||||
|
||||
|
||||
class AttributeGroup(SchemaObject):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:attributeGroup/> node.
|
||||
@cvar childtags: A list of valid child node names
|
||||
@type childtags: (I{str},...)
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return ('attribute', 'attributeGroup')
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = AttrGroupQuery(self.ref)
|
||||
ag = query.execute(self.schema)
|
||||
if ag is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
deps.append(ag)
|
||||
midx = 0
|
||||
return (midx, deps)
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
self.rawchildren = other.rawchildren
|
||||
|
||||
def description(self):
|
||||
return ('name', 'ref',)
|
||||
|
||||
|
||||
class Simple(SchemaObject):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:simpleType/> node
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return ('restriction', 'any', 'list',)
|
||||
|
||||
def enum(self):
|
||||
for child, ancestry in self.children():
|
||||
if isinstance(child, Enumeration):
|
||||
return True
|
||||
return False
|
||||
|
||||
def mixed(self):
|
||||
return len(self)
|
||||
|
||||
def description(self):
|
||||
return ('name',)
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
for c in self.rawchildren:
|
||||
if c.restriction():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class List(SchemaObject):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:list/> node
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return ()
|
||||
|
||||
def description(self):
|
||||
return ('name',)
|
||||
|
||||
def xslist(self):
|
||||
return True
|
||||
|
||||
|
||||
class Restriction(SchemaObject):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:restriction/> node
|
||||
"""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.ref = root.get('base')
|
||||
|
||||
def childtags(self):
|
||||
return ('enumeration', 'attribute', 'attributeGroup')
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = TypeQuery(self.ref)
|
||||
super = query.execute(self.schema)
|
||||
if super is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
if not super.builtin():
|
||||
deps.append(super)
|
||||
midx = 0
|
||||
return (midx, deps)
|
||||
|
||||
def restriction(self):
|
||||
return True
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
filter = Filter(False, self.rawchildren)
|
||||
self.prepend(self.rawchildren, other.rawchildren, filter)
|
||||
|
||||
def description(self):
|
||||
return ('ref',)
|
||||
|
||||
|
||||
class Collection(SchemaObject):
|
||||
"""
|
||||
Represents an (xsd) schema collection node:
|
||||
- sequence
|
||||
- choice
|
||||
- all
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return ('element', 'sequence', 'all', 'choice', 'any', 'group')
|
||||
|
||||
|
||||
class Sequence(Collection):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:sequence/> node.
|
||||
"""
|
||||
def sequence(self):
|
||||
return True
|
||||
|
||||
|
||||
class All(Collection):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:all/> node.
|
||||
"""
|
||||
def all(self):
|
||||
return True
|
||||
|
||||
class Choice(Collection):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:choice/> node.
|
||||
"""
|
||||
def choice(self):
|
||||
return True
|
||||
|
||||
|
||||
class ComplexContent(SchemaObject):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:complexContent/> node.
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return ('attribute', 'attributeGroup', 'extension', 'restriction')
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
for c in self.rawchildren:
|
||||
if c.restriction():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class SimpleContent(SchemaObject):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:simpleContent/> node.
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return ('extension', 'restriction')
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
for c in self.rawchildren:
|
||||
if c.restriction():
|
||||
return True
|
||||
return False
|
||||
|
||||
def mixed(self):
|
||||
return len(self)
|
||||
|
||||
|
||||
class Enumeration(Content):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:enumeration/> node
|
||||
"""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
Content.__init__(self, schema, root)
|
||||
self.name = root.get('value')
|
||||
|
||||
def enum(self):
|
||||
return True
|
||||
|
||||
|
||||
class Element(TypedContent):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:element/> node.
|
||||
"""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
TypedContent.__init__(self, schema, root)
|
||||
a = root.get('form')
|
||||
if a is not None:
|
||||
self.form_qualified = ( a == 'qualified' )
|
||||
a = self.root.get('nillable')
|
||||
if a is not None:
|
||||
self.nillable = ( a in ('1', 'true') )
|
||||
self.implany()
|
||||
|
||||
def implany(self):
|
||||
"""
|
||||
Set the type as any when implicit.
|
||||
An implicit <xs:any/> is when an element has not
|
||||
body and no type defined.
|
||||
@return: self
|
||||
@rtype: L{Element}
|
||||
"""
|
||||
if self.type is None and \
|
||||
self.ref is None and \
|
||||
self.root.isempty():
|
||||
self.type = self.anytype()
|
||||
return self
|
||||
|
||||
def childtags(self):
|
||||
return ('attribute', 'simpleType', 'complexType', 'any',)
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
for c in self.rawchildren:
|
||||
if c.restriction():
|
||||
return True
|
||||
return False
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = ElementQuery(self.ref)
|
||||
e = query.execute(self.schema)
|
||||
if e is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
deps.append(e)
|
||||
midx = 0
|
||||
return (midx, deps)
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
self.rawchildren = other.rawchildren
|
||||
|
||||
def description(self):
|
||||
return ('name', 'ref', 'type')
|
||||
|
||||
def anytype(self):
|
||||
""" create an xsd:anyType reference """
|
||||
p,u = Namespace.xsdns
|
||||
mp = self.root.findPrefix(u)
|
||||
if mp is None:
|
||||
mp = p
|
||||
self.root.addPrefix(p, u)
|
||||
return ':'.join((mp, 'anyType'))
|
||||
|
||||
|
||||
class Extension(SchemaObject):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:extension/> node.
|
||||
"""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.ref = root.get('base')
|
||||
|
||||
def childtags(self):
|
||||
return ('attribute',
|
||||
'attributeGroup',
|
||||
'sequence',
|
||||
'all',
|
||||
'choice',
|
||||
'group')
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = TypeQuery(self.ref)
|
||||
super = query.execute(self.schema)
|
||||
if super is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
if not super.builtin():
|
||||
deps.append(super)
|
||||
midx = 0
|
||||
return (midx, deps)
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
filter = Filter(False, self.rawchildren)
|
||||
self.prepend(self.rawchildren, other.rawchildren, filter)
|
||||
|
||||
def extension(self):
|
||||
return ( self.ref is not None )
|
||||
|
||||
def description(self):
|
||||
return ('ref',)
|
||||
|
||||
|
||||
class Import(SchemaObject):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:import/> node
|
||||
@cvar locations: A dictionary of namespace locations.
|
||||
@type locations: dict
|
||||
@ivar ns: The imported namespace.
|
||||
@type ns: str
|
||||
@ivar location: The (optional) location.
|
||||
@type location: namespace-uri
|
||||
@ivar opened: Opened and I{imported} flag.
|
||||
@type opened: boolean
|
||||
"""
|
||||
|
||||
locations = {}
|
||||
|
||||
@classmethod
|
||||
def bind(cls, ns, location=None):
|
||||
"""
|
||||
Bind a namespace to a schema location (URI).
|
||||
This is used for imports that don't specify a schemaLocation.
|
||||
@param ns: A namespace-uri.
|
||||
@type ns: str
|
||||
@param location: The (optional) schema location for the
|
||||
namespace. (default=ns).
|
||||
@type location: str
|
||||
"""
|
||||
if location is None:
|
||||
location = ns
|
||||
cls.locations[ns] = location
|
||||
|
||||
def __init__(self, schema, root):
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.ns = (None, root.get('namespace'))
|
||||
self.location = root.get('schemaLocation')
|
||||
if self.location is None:
|
||||
self.location = self.locations.get(self.ns[1])
|
||||
self.opened = False
|
||||
|
||||
def open(self, options):
|
||||
"""
|
||||
Open and import the refrenced schema.
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
@return: The referenced schema.
|
||||
@rtype: L{Schema}
|
||||
"""
|
||||
if self.opened:
|
||||
return
|
||||
self.opened = True
|
||||
log.debug('%s, importing ns="%s", location="%s"', self.id, self.ns[1], self.location)
|
||||
result = self.locate()
|
||||
if result is None:
|
||||
if self.location is None:
|
||||
log.debug('imported schema (%s) not-found', self.ns[1])
|
||||
else:
|
||||
result = self.download(options)
|
||||
log.debug('imported:\n%s', result)
|
||||
return result
|
||||
|
||||
def locate(self):
|
||||
""" find the schema locally """
|
||||
if self.ns[1] == self.schema.tns[1]:
|
||||
return None
|
||||
else:
|
||||
return self.schema.locate(self.ns)
|
||||
|
||||
def download(self, options):
|
||||
""" download the schema """
|
||||
url = self.location
|
||||
try:
|
||||
if '://' not in url:
|
||||
url = urljoin(self.schema.baseurl, url)
|
||||
reader = DocumentReader(options)
|
||||
d = reader.open(url)
|
||||
root = d.root()
|
||||
root.set('url', url)
|
||||
return self.schema.instance(root, url, options)
|
||||
except TransportError:
|
||||
msg = 'imported schema (%s) at (%s), failed' % (self.ns[1], url)
|
||||
log.error('%s, %s', self.id, msg, exc_info=True)
|
||||
raise Exception(msg)
|
||||
|
||||
def description(self):
|
||||
return ('ns', 'location')
|
||||
|
||||
|
||||
class Include(SchemaObject):
|
||||
"""
|
||||
Represents an (xsd) schema <xs:include/> node
|
||||
@ivar location: The (optional) location.
|
||||
@type location: namespace-uri
|
||||
@ivar opened: Opened and I{imported} flag.
|
||||
@type opened: boolean
|
||||
"""
|
||||
|
||||
locations = {}
|
||||
|
||||
def __init__(self, schema, root):
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.location = root.get('schemaLocation')
|
||||
if self.location is None:
|
||||
self.location = self.locations.get(self.ns[1])
|
||||
self.opened = False
|
||||
|
||||
def open(self, options):
|
||||
"""
|
||||
Open and include the refrenced schema.
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
@return: The referenced schema.
|
||||
@rtype: L{Schema}
|
||||
"""
|
||||
if self.opened:
|
||||
return
|
||||
self.opened = True
|
||||
log.debug('%s, including location="%s"', self.id, self.location)
|
||||
result = self.download(options)
|
||||
log.debug('included:\n%s', result)
|
||||
return result
|
||||
|
||||
def download(self, options):
|
||||
""" download the schema """
|
||||
url = self.location
|
||||
try:
|
||||
if '://' not in url:
|
||||
url = urljoin(self.schema.baseurl, url)
|
||||
reader = DocumentReader(options)
|
||||
d = reader.open(url)
|
||||
root = d.root()
|
||||
root.set('url', url)
|
||||
self.__applytns(root)
|
||||
return self.schema.instance(root, url, options)
|
||||
except TransportError:
|
||||
msg = 'include schema at (%s), failed' % url
|
||||
log.error('%s, %s', self.id, msg, exc_info=True)
|
||||
raise Exception(msg)
|
||||
|
||||
def __applytns(self, root):
|
||||
""" make sure included schema has same tns. """
|
||||
TNS = 'targetNamespace'
|
||||
tns = root.get(TNS)
|
||||
if tns is None:
|
||||
tns = self.schema.tns[1]
|
||||
root.set(TNS, tns)
|
||||
else:
|
||||
if self.schema.tns[1] != tns:
|
||||
raise Exception, '%s mismatch' % TNS
|
||||
|
||||
|
||||
def description(self):
|
||||
return ('location')
|
||||
|
||||
|
||||
class Attribute(TypedContent):
|
||||
"""
|
||||
Represents an (xsd) <attribute/> node
|
||||
"""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
TypedContent.__init__(self, schema, root)
|
||||
self.use = root.get('use', default='')
|
||||
|
||||
def childtags(self):
|
||||
return ('restriction',)
|
||||
|
||||
def isattr(self):
|
||||
return True
|
||||
|
||||
def get_default(self):
|
||||
"""
|
||||
Gets the <xs:attribute default=""/> attribute value.
|
||||
@return: The default value for the attribute
|
||||
@rtype: str
|
||||
"""
|
||||
return self.root.get('default', default='')
|
||||
|
||||
def optional(self):
|
||||
return ( self.use != 'required' )
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = AttrQuery(self.ref)
|
||||
a = query.execute(self.schema)
|
||||
if a is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
deps.append(a)
|
||||
midx = 0
|
||||
return (midx, deps)
|
||||
|
||||
def description(self):
|
||||
return ('name', 'ref', 'type')
|
||||
|
||||
|
||||
class Any(Content):
|
||||
"""
|
||||
Represents an (xsd) <any/> node
|
||||
"""
|
||||
|
||||
def get_child(self, name):
|
||||
root = self.root.clone()
|
||||
root.set('note', 'synthesized (any) child')
|
||||
child = Any(self.schema, root)
|
||||
return (child, [])
|
||||
|
||||
def get_attribute(self, name):
|
||||
root = self.root.clone()
|
||||
root.set('note', 'synthesized (any) attribute')
|
||||
attribute = Any(self.schema, root)
|
||||
return (attribute, [])
|
||||
|
||||
def any(self):
|
||||
return True
|
||||
|
||||
|
||||
class Factory:
|
||||
"""
|
||||
@cvar tags: A factory to create object objects based on tag.
|
||||
@type tags: {tag:fn,}
|
||||
"""
|
||||
|
||||
tags =\
|
||||
{
|
||||
'import' : Import,
|
||||
'include' : Include,
|
||||
'complexType' : Complex,
|
||||
'group' : Group,
|
||||
'attributeGroup' : AttributeGroup,
|
||||
'simpleType' : Simple,
|
||||
'list' : List,
|
||||
'element' : Element,
|
||||
'attribute' : Attribute,
|
||||
'sequence' : Sequence,
|
||||
'all' : All,
|
||||
'choice' : Choice,
|
||||
'complexContent' : ComplexContent,
|
||||
'simpleContent' : SimpleContent,
|
||||
'restriction' : Restriction,
|
||||
'enumeration' : Enumeration,
|
||||
'extension' : Extension,
|
||||
'any' : Any,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def maptag(cls, tag, fn):
|
||||
"""
|
||||
Map (override) tag => I{class} mapping.
|
||||
@param tag: An xsd tag name.
|
||||
@type tag: str
|
||||
@param fn: A function or class.
|
||||
@type fn: fn|class.
|
||||
"""
|
||||
cls.tags[tag] = fn
|
||||
|
||||
@classmethod
|
||||
def create(cls, root, schema):
|
||||
"""
|
||||
Create an object based on the root tag name.
|
||||
@param root: An XML root element.
|
||||
@type root: L{Element}
|
||||
@param schema: A schema object.
|
||||
@type schema: L{schema.Schema}
|
||||
@return: The created object.
|
||||
@rtype: L{SchemaObject}
|
||||
"""
|
||||
fn = cls.tags.get(root.name)
|
||||
if fn is not None:
|
||||
return fn(schema, root)
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def build(cls, root, schema, filter=('*',)):
|
||||
"""
|
||||
Build an xsobject representation.
|
||||
@param root: An schema XML root.
|
||||
@type root: L{sax.element.Element}
|
||||
@param filter: A tag filter.
|
||||
@type filter: [str,...]
|
||||
@return: A schema object graph.
|
||||
@rtype: L{sxbase.SchemaObject}
|
||||
"""
|
||||
children = []
|
||||
for node in root.getChildren(ns=Namespace.xsdns):
|
||||
if '*' in filter or node.name in filter:
|
||||
child = cls.create(node, schema)
|
||||
if child is None:
|
||||
continue
|
||||
children.append(child)
|
||||
c = cls.build(node, schema, child.childtags())
|
||||
child.rawchildren = c
|
||||
return children
|
||||
|
||||
@classmethod
|
||||
def collate(cls, children):
|
||||
imports = []
|
||||
elements = {}
|
||||
attributes = {}
|
||||
types = {}
|
||||
groups = {}
|
||||
agrps = {}
|
||||
for c in children:
|
||||
if isinstance(c, (Import, Include)):
|
||||
imports.append(c)
|
||||
continue
|
||||
if isinstance(c, Attribute):
|
||||
attributes[c.qname] = c
|
||||
continue
|
||||
if isinstance(c, Element):
|
||||
elements[c.qname] = c
|
||||
continue
|
||||
if isinstance(c, Group):
|
||||
groups[c.qname] = c
|
||||
continue
|
||||
if isinstance(c, AttributeGroup):
|
||||
agrps[c.qname] = c
|
||||
continue
|
||||
types[c.qname] = c
|
||||
for i in imports:
|
||||
children.remove(i)
|
||||
return (children, imports, attributes, elements, types, groups, agrps)
|
||||
|
||||
|
||||
|
||||
|
||||
#######################################################
|
||||
# Static Import Bindings :-(
|
||||
#######################################################
|
||||
Import.bind(
|
||||
'http://schemas.xmlsoap.org/soap/encoding/',
|
||||
'suds://schemas.xmlsoap.org/soap/encoding/')
|
||||
Import.bind(
|
||||
'http://www.w3.org/XML/1998/namespace',
|
||||
'http://www.w3.org/2001/xml.xsd')
|
||||
Import.bind(
|
||||
'http://www.w3.org/2001/XMLSchema',
|
||||
'http://www.w3.org/2001/XMLSchema.xsd')
|
||||
@@ -0,0 +1,274 @@
|
||||
# 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 )
|
||||
|
||||
"""
|
||||
The I{sxbuiltin} module provides classes that represent
|
||||
XSD I{builtin} schema objects.
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
from suds import *
|
||||
from suds.xsd import *
|
||||
from suds.sax.date import *
|
||||
from suds.xsd.sxbase import XBuiltin
|
||||
import datetime as dt
|
||||
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class XString(XBuiltin):
|
||||
"""
|
||||
Represents an (xsd) <xs:string/> node
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class XAny(XBuiltin):
|
||||
"""
|
||||
Represents an (xsd) <any/> node
|
||||
"""
|
||||
|
||||
def __init__(self, schema, name):
|
||||
XBuiltin.__init__(self, schema, name)
|
||||
self.nillable = False
|
||||
|
||||
def get_child(self, name):
|
||||
child = XAny(self.schema, name)
|
||||
return (child, [])
|
||||
|
||||
def any(self):
|
||||
return True
|
||||
|
||||
|
||||
class XBoolean(XBuiltin):
|
||||
"""
|
||||
Represents an (xsd) boolean builtin type.
|
||||
"""
|
||||
|
||||
translation = (
|
||||
{ '1':True,'true':True,'0':False,'false':False },
|
||||
{ True:'true',1:'true',False:'false',0:'false' },
|
||||
)
|
||||
|
||||
def translate(self, value, topython=True):
|
||||
if topython:
|
||||
if isinstance(value, basestring):
|
||||
return XBoolean.translation[0].get(value)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
if isinstance(value, (bool,int)):
|
||||
return XBoolean.translation[1].get(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
class XInteger(XBuiltin):
|
||||
"""
|
||||
Represents an (xsd) xs:int builtin type.
|
||||
"""
|
||||
|
||||
def translate(self, value, topython=True):
|
||||
if topython:
|
||||
if isinstance(value, basestring) and len(value):
|
||||
return int(value)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
if isinstance(value, int):
|
||||
return str(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
class XLong(XBuiltin):
|
||||
"""
|
||||
Represents an (xsd) xs:long builtin type.
|
||||
"""
|
||||
|
||||
def translate(self, value, topython=True):
|
||||
if topython:
|
||||
if isinstance(value, basestring) and len(value):
|
||||
return long(value)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
if isinstance(value, (int,long)):
|
||||
return str(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
class XFloat(XBuiltin):
|
||||
"""
|
||||
Represents an (xsd) xs:float builtin type.
|
||||
"""
|
||||
|
||||
def translate(self, value, topython=True):
|
||||
if topython:
|
||||
if isinstance(value, basestring) and len(value):
|
||||
return float(value)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
if isinstance(value, float):
|
||||
return str(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
class XDate(XBuiltin):
|
||||
"""
|
||||
Represents an (xsd) xs:date builtin type.
|
||||
"""
|
||||
|
||||
def translate(self, value, topython=True):
|
||||
if topython:
|
||||
if isinstance(value, basestring) and len(value):
|
||||
return Date(value).date
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
if isinstance(value, dt.date):
|
||||
return str(Date(value))
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
class XTime(XBuiltin):
|
||||
"""
|
||||
Represents an (xsd) xs:time builtin type.
|
||||
"""
|
||||
|
||||
def translate(self, value, topython=True):
|
||||
if topython:
|
||||
if isinstance(value, basestring) and len(value):
|
||||
return Time(value).time
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
if isinstance(value, dt.date):
|
||||
return str(Time(value))
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
class XDateTime(XBuiltin):
|
||||
"""
|
||||
Represents an (xsd) xs:datetime builtin type.
|
||||
"""
|
||||
|
||||
def translate(self, value, topython=True):
|
||||
if topython:
|
||||
if isinstance(value, basestring) and len(value):
|
||||
return DateTime(value).datetime
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
if isinstance(value, dt.date):
|
||||
return str(DateTime(value))
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
class Factory:
|
||||
|
||||
tags =\
|
||||
{
|
||||
# any
|
||||
'anyType' : XAny,
|
||||
# strings
|
||||
'string' : XString,
|
||||
'normalizedString' : XString,
|
||||
'ID' : XString,
|
||||
'Name' : XString,
|
||||
'QName' : XString,
|
||||
'NCName' : XString,
|
||||
'anySimpleType' : XString,
|
||||
'anyURI' : XString,
|
||||
'NOTATION' : XString,
|
||||
'token' : XString,
|
||||
'language' : XString,
|
||||
'IDREFS' : XString,
|
||||
'ENTITIES' : XString,
|
||||
'IDREF' : XString,
|
||||
'ENTITY' : XString,
|
||||
'NMTOKEN' : XString,
|
||||
'NMTOKENS' : XString,
|
||||
# binary
|
||||
'hexBinary' : XString,
|
||||
'base64Binary' : XString,
|
||||
# integers
|
||||
'int' : XInteger,
|
||||
'integer' : XInteger,
|
||||
'unsignedInt' : XInteger,
|
||||
'positiveInteger' : XInteger,
|
||||
'negativeInteger' : XInteger,
|
||||
'nonPositiveInteger' : XInteger,
|
||||
'nonNegativeInteger' : XInteger,
|
||||
# longs
|
||||
'long' : XLong,
|
||||
'unsignedLong' : XLong,
|
||||
# shorts
|
||||
'short' : XInteger,
|
||||
'unsignedShort' : XInteger,
|
||||
'byte' : XInteger,
|
||||
'unsignedByte' : XInteger,
|
||||
# floats
|
||||
'float' : XFloat,
|
||||
'double' : XFloat,
|
||||
'decimal' : XFloat,
|
||||
# dates & times
|
||||
'date' : XDate,
|
||||
'time' : XTime,
|
||||
'dateTime': XDateTime,
|
||||
'duration': XString,
|
||||
'gYearMonth' : XString,
|
||||
'gYear' : XString,
|
||||
'gMonthDay' : XString,
|
||||
'gDay' : XString,
|
||||
'gMonth' : XString,
|
||||
# boolean
|
||||
'boolean' : XBoolean,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def maptag(cls, tag, fn):
|
||||
"""
|
||||
Map (override) tag => I{class} mapping.
|
||||
@param tag: An xsd tag name.
|
||||
@type tag: str
|
||||
@param fn: A function or class.
|
||||
@type fn: fn|class.
|
||||
"""
|
||||
cls.tags[tag] = fn
|
||||
|
||||
@classmethod
|
||||
def create(cls, schema, name):
|
||||
"""
|
||||
Create an object based on the root tag name.
|
||||
@param schema: A schema object.
|
||||
@type schema: L{schema.Schema}
|
||||
@param name: The name.
|
||||
@type name: str
|
||||
@return: The created object.
|
||||
@rtype: L{XBuiltin}
|
||||
"""
|
||||
fn = cls.tags.get(name)
|
||||
if fn is not None:
|
||||
return fn(schema, name)
|
||||
else:
|
||||
return XBuiltin(schema, name)
|
||||
Reference in New Issue
Block a user