source: trunk/suds/xsd/schema.py @ 358

Revision 358, 11.7 KB checked in by jortel, 5 years ago (diff)

Add support for <wsdl:import/> of schema files into the wsdl root <definitions/>. This clearly violates the WSDL 1.1 and 2.0 specifications (including the wsdl schema: http://www.w3.org/2007/06/wsdl/wsdl20.xsd ) but since ( http://www.w3.org/TR/wsdl#_style ) show doing this as an example, then it must be supported. Bump trunk to beta (R358-20081029)

Line 
1# This program is free software; you can redistribute it and/or modify
2# it under the terms of the (LGPL) GNU Lesser General Public License as
3# published by the Free Software Foundation; either version 3 of the
4# License, or (at your option) any later version.
5#
6# This program is distributed in the hope that it will be useful,
7# but WITHOUT ANY WARRANTY; without even the implied warranty of
8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9# GNU Library Lesser General Public License for more details at
10# ( http://www.gnu.org/licenses/lgpl.html ).
11#
12# You should have received a copy of the GNU Lesser General Public License
13# along with this program; if not, write to the Free Software
14# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15# written by: Jeff Ortel ( jortel@redhat.com )
16
17"""
18The I{schema} module provides a intelligent representation of
19an XSD schema.  The I{raw} model is the XML tree and the I{model}
20is the denormalized, objectified and intelligent view of the schema.
21Most of the I{value-add} provided by the model is centered around
22tranparent referenced type resolution and targeted denormalization.
23"""
24
25from logging import getLogger
26import suds.metrics
27from suds import *
28from suds.xsd import *
29from suds.xsd.sxbuiltin import *
30from suds.xsd.sxbasic import Factory as BasicFactory
31from suds.xsd.sxbuiltin import Factory as BuiltinFactory
32from suds.xsd.sxbase import SchemaObject
33from suds.sax import splitPrefix, Namespace
34
35log = getLogger(__name__)
36
37
38class SchemaCollection:
39    """
40    A collection of schema objects.  This class is needed because WSDLs
41    may contain more then one <schema/> node.
42    @ivar wsdl: A wsdl object.
43    @type wsdl: L{suds.wsdl.Definitions}
44    @ivar children: A list contained schemas.
45    @type children: [L{Schema},...]
46    @ivar namespaces: A dictionary of contained schemas by namespace.
47    @type namespaces: {str:L{Schema}}
48    """
49   
50    def __init__(self, wsdl):
51        """
52        @param wsdl: A wsdl object.
53        @type wsdl: L{suds.wsdl.Definitions}
54        """
55        self.wsdl = wsdl
56        self.children = []
57        self.namespaces = {}
58       
59    def add(self, schema):
60        """
61        Add a schema node to the collection.
62        @param schema: A <schema/> entry.
63        @type schema: (L{suds.wsdl.Definitions},L{sax.element.Element})
64        """
65        root, wsdl = schema
66        child = Schema(root, wsdl.url, self)
67        self.children.append(child)
68        self.namespaces[child.tns[1]] = child
69       
70    def load(self):
71        """
72        Load the schema objects for the root nodes.
73            - de-references schemas
74            - flatten schemas
75            - merge schemas
76        @return: The merged schema.
77        @rtype: L{Schema}
78        """
79        for child in self.children:
80            child.build()
81        for child in self.children:
82            child.open_imports()
83        log.debug('loaded:\n%s', self)
84        merged = self.merge()
85        log.debug('merged\n%s', merged)
86        merged.dereference()
87        merged.flatten()
88        log.debug('flattened-merged\n%s', merged)
89        return merged
90       
91    def locate(self, ns):
92        """
93        Find a schema by namespace.  Only the URI portion of
94        the namespace is compared to each schema's I{targetNamespace}
95        @param ns: A namespace.
96        @type ns: (prefix,URI)
97        @return: The schema matching the namesapce, else None.
98        @rtype: L{Schema}
99        """
100        return self.namespaces.get(ns[1])
101   
102    def merge(self):
103        """
104        Merge the contained schemas into one.
105        @return: The merged schema.
106        @rtype: L{Schema}
107        """
108        if len(self):
109            schema = self.children[0]
110            for s in self.children[1:]:
111                schema.merge(s)
112            return schema
113        else:
114            return None
115   
116    def __len__(self):
117        return len(self.children)
118   
119    def __str__(self):
120        return unicode(self).encode('utf-8')
121   
122    def __unicode__(self):
123        result = ['\nschema collection']
124        for s in self.children:
125            result.append(s.str(1))
126        return '\n'.join(result)
127
128
129class Schema:
130    """
131    The schema is an objectification of a <schema/> (xsd) definition.
132    It provides inspection, lookup and type resolution.
133    @ivar root: The root node.
134    @type root: L{sax.element.Element}
135    @ivar baseurl: The I{base} URL for this schema.
136    @type baseurl: str
137    @ivar container: A schema collection containing this schema.
138    @type container: L{SchemaCollection}
139    @ivar types: A schema types cache.
140    @type types: {name:L{SchemaObject}}
141    @ivar groups: A schema groups cache.
142    @type groups: {name:L{SchemaObject}}
143    @ivar children: A list of children.
144    @type children: [L{SchemaObject},...]
145    @ivar imports: A list of import objects.
146    @type imports: [L{SchemaObject},...]
147    @ivar merged: A flag indicating this schema has been merged.
148    @type merged: bool
149    @ivar form_qualified: The flag indicating:
150        (@elementFormDefault).
151    @type form_qualified: bool
152    """
153   
154    Tag = 'schema'
155   
156    def __init__(self, root, baseurl, container=None):
157        """
158        @param root: The xml root.
159        @type root: L{sax.element.Element}
160        @param baseurl: The base url used for importing.
161        @type baseurl: basestring
162        @param container: An optional container.
163        @type container: L{SchemaCollection}
164        """
165        self.root = root
166        self.id = objid(self)
167        self.tns = self.mktns()
168        self.baseurl = baseurl
169        self.container = container
170        self.children = []
171        self.types = {}
172        self.imports = []
173        self.elements = {}
174        self.attributes = {}
175        self.groups = {}
176        self.agrps = {}
177        self.merged = False
178        form = self.root.get('elementFormDefault')
179        if form is None:
180            self.form_qualified = False
181        else:
182            self.form_qualified = ( form == 'qualified' )
183        if container is None:
184            self.build()
185            self.open_imports()
186            self.dereference()
187            self.flatten()
188               
189    def mktns(self):
190        """
191        Make the schema's target namespace.
192        @return: The namespace representation of the schema's
193            targetNamespace value.
194        @rtype: (prefix, uri)
195        """
196        tns = [None, self.root.get('targetNamespace')]
197        if tns[1] is not None:
198            tns[0] = self.root.findPrefix(tns[1])
199        return tuple(tns)
200               
201    def build(self):
202        """
203        Build the schema (object graph) using the root node
204        using the factory.
205            - Build the graph.
206            - Collate the children.
207        """
208        attributes, self.children = BasicFactory.build(self.root, self)
209        collated = BasicFactory.collate(attributes)
210        self.attributes = collated[2]
211        collated = BasicFactory.collate(self.children)
212        self.children = collated[0]
213        self.imports = collated[1]
214        self.elements = collated[3]
215        self.types = collated[4]
216        self.groups = collated[5]
217        self.agrps = collated[6]
218       
219    def merge(self, schema):
220        """
221        Merge the contents from the schema.  Only objects not already contained
222        in this schema's collections are merged.  This is to provide for bidirectional
223        import which produce cyclic includes.
224        @returns: self
225        @rtype: L{Schema}
226        """
227        for item in schema.attributes.items():
228            if item[0] in self.attributes:
229                continue
230            self.attributes[item[0]] = item[1]
231        for item in schema.elements.items():
232            if item[0] in self.elements:
233                continue
234            self.elements[item[0]] = item[1]
235            self.children.append(item[1])
236        for item in schema.types.items():
237            if item[0] in self.types:
238                continue
239            self.types[item[0]] = item[1]
240            self.children.append(item[1])
241        for item in schema.groups.items():
242            if item[0] in self.groups:
243                continue
244            self.groups[item[0]] = item[1]
245            self.children.append(item[1])
246        for item in schema.agrps.items():
247            if item[0] in self.agrps:
248                continue
249            self.agrps[item[0]] = item[1]
250            self.children.append(item[1])
251        schema.merged = True
252        return self
253       
254    def open_imports(self):
255        """
256        Instruct all contained L{sxbasic.Import} children to import
257        the schema's which they reference.  The contents of the
258        imported schema are I{merged} in.
259        """
260        for imp in self.imports:
261            imported = imp.open()
262            if imported is None:
263                continue
264            imported.open_imports()
265            self.merge(imported)
266       
267    def locate(self, ns):
268        """
269        Find a schema by namespace.  Only the URI portion of
270        the namespace is compared to each schema's I{targetNamespace}.
271        The request is passed to the container.
272        @param ns: A namespace.
273        @type ns: (prefix,URI)
274        @return: The schema matching the namesapce, else None.
275        @rtype: L{Schema}
276        """
277        if self.container is not None:
278            return self.container.locate(ns)
279        else:
280            return None
281           
282    def dereference(self):
283        """
284        Instruct all children to perform dereferencing.
285        """
286        for c in self.children:
287            c.dereference()
288       
289    def flatten(self):
290        """
291        Instruct all children to I{flatten}.
292        """
293        for c in self.children:
294            c.flatten()
295
296    def custom(self, ref, context=None):
297        """
298        Get whether the specified reference is B{not} an (xs) builtin.
299        @param ref: A str or qref.
300        @type ref: (str|qref)
301        @return: True if B{not} a builtin, else False.
302        @rtype: bool
303        """
304        if ref is None:
305            return True
306        else:
307            return ( not self.builtin(ref, context) )
308   
309    def builtin(self, ref, context=None):
310        """
311        Get whether the specified reference is an (xs) builtin.
312        @param ref: A str or qref.
313        @type ref: (str|qref)
314        @return: True if builtin, else False.
315        @rtype: bool
316        """
317        w3 = 'http://www.w3.org'
318        try:
319            if isqref(ref):
320                ns = ref[1]
321                return ns.startswith(w3)
322            if context is None:
323                context = self.root   
324            prefix = splitPrefix(ref)[0]
325            prefixes = context.findPrefixes(w3, 'startswith')
326            return (prefix in prefixes)
327        except:
328            return False
329       
330    def instance(self, root, url):
331        """
332        Create and return an new schema object using the
333        specified I{root} and I{url}.
334        @param root: A schema root node.
335        @type root: L{sax.element.Element}
336        @param url: A base URL.
337        @type url: str
338        @return: The newly created schema object.
339        @rtype: L{Schema}
340        @note: This is only used by Import children.
341        """
342        return Schema(root, url)
343
344    def str(self, indent=0):
345        tab = '%*s'%(indent*3, '')
346        result = []
347        result.append('%s%s' % (tab, self.id))
348        result.append('%s(raw)' % tab)
349        result.append(self.root.str(indent+1))
350        result.append('%s(model)' % tab)
351        for c in self.children:
352            result.append(c.str(indent+1))
353        result.append('')
354        return '\n'.join(result)
355       
356    def __repr__(self):
357        myrep = '<%s tns="%s"/>' % (self.id, self.tns[1])
358        return myrep.encode('utf-8')
359   
360    def __str__(self):
361        return unicode(self).encode('utf-8')
362   
363    def __unicode__(self):
364        return self.str()
365
366
367
Note: See TracBrowser for help on using the repository browser.