1
2#
3# spyne - Copyright (C) Spyne contributors.
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Lesser General Public
7# License as published by the Free Software Foundation; either
8# version 2.1 of the License, or (at your option) any later version.
9#
10# This library is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with this library; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
18#
19
20
21"""The `spyne.util.xml` module contains various Xml and Xml Schema related
22utility functions.
23"""
24from inspect import isgenerator
25
26from lxml import etree
27
28from os.path import dirname
29from os.path import abspath
30
31from spyne import ServiceBase, Application, srpc
32from spyne.context import FakeContext
33from spyne.interface import Interface
34from spyne.interface.xml_schema import XmlSchema
35from spyne.interface.xml_schema.parser import XmlSchemaParser, Thier_repr, PARSER
36from spyne.protocol import ProtocolMixin
37from spyne.protocol.cloth import XmlCloth
38
39from spyne.protocol.xml import XmlDocument
40from spyne.util.appreg import unregister_application
41from spyne.util.six import BytesIO
42from spyne.util.tlist import tlist
43
44
45class FakeApplication(object):
46    def __init__(self, default_namespace):
47        self.tns = default_namespace
48        self.services = ()
49        self.classes = ()
50
51
52def get_schema_documents(models, default_namespace=None):
53    """Returns the schema documents in a dict whose keys are namespace prefixes
54    and values are Element objects.
55
56    :param models: A list of spyne.model classes that will be represented in
57                   the schema.
58    """
59
60    if default_namespace is None:
61        default_namespace = models[0].get_namespace()
62
63    fake_app = FakeApplication(default_namespace)
64
65    interface = Interface(fake_app)
66    for m in models:
67        m.resolve_namespace(m, default_namespace)
68        interface.add_class(m)
69    interface.populate_interface(fake_app)
70
71    document = XmlSchema(interface)
72    document.build_interface_document()
73
74    return document.get_interface_document()
75
76
77def get_validation_schema(models, default_namespace=None):
78    """Returns the validation schema object for the given models.
79
80    :param models: A list of spyne.model classes that will be represented in
81                   the schema.
82    """
83
84    if default_namespace is None:
85        default_namespace = models[0].get_namespace()
86
87    fake_app = FakeApplication(default_namespace)
88
89    interface = Interface(fake_app)
90    for m in models:
91        m.resolve_namespace(m, default_namespace)
92        interface.add_class(m)
93
94    schema = XmlSchema(interface)
95    schema.build_validation_schema()
96
97    return schema.validation_schema
98
99
100def _dig(par):
101    for elt in par:
102        elt.tag = elt.tag.split('}')[-1]
103        _dig(elt)
104
105
106_xml_object = XmlDocument()
107
108
109def get_object_as_xml(inst, cls=None, root_tag_name=None, no_namespace=False):
110    """Returns an ElementTree representation of a
111    :class:`spyne.model.complex.ComplexModel` subclass.
112
113    :param inst: The instance of the class to be serialized.
114    :param cls: The class to be serialized. Optional.
115    :param root_tag_name: The root tag string to use. Defaults to the output of
116        ``value.__class__.get_type_name_ns()``.
117    :param no_namespace: When true, namespace information is discarded.
118    """
119
120    if cls is None:
121        cls = inst.__class__
122
123    parent = etree.Element("parent")
124    _xml_object.to_parent(None, cls, inst, parent, cls.get_namespace(),
125                                                                  root_tag_name)
126    if no_namespace:
127        _dig(parent)
128        etree.cleanup_namespaces(parent)
129
130    return parent[0]
131
132
133def get_object_as_xml_polymorphic(inst, cls=None, root_tag_name=None,
134                                                            no_namespace=False):
135    """Returns an ElementTree representation of a
136    :class:`spyne.model.complex.ComplexModel` subclass.
137
138    :param inst: The instance of the class to be serialized.
139    :param cls: The class to be serialized. Optional.
140    :param root_tag_name: The root tag string to use. Defaults to the output of
141        ``value.__class__.get_type_name_ns()``.
142    :param no_namespace: When true, namespace information is discarded.
143    """
144
145    if cls is None:
146        cls = inst.__class__
147
148    if no_namespace:
149        app = Application([ServiceBase], tns="",
150                                     out_protocol=XmlDocument(polymorphic=True))
151    else:
152        tns = cls.get_namespace()
153        if tns is None:
154            raise ValueError(
155                "Either set a namespace for %r or pass no_namespace=True"
156                                                                      % (cls, ))
157
158        class _DummyService(ServiceBase):
159            @srpc(cls)
160            def f(_):
161                pass
162
163        app = Application([_DummyService], tns=tns,
164                                     out_protocol=XmlDocument(polymorphic=True))
165
166    unregister_application(app)
167
168    parent = etree.Element("parent", nsmap=app.interface.nsmap)
169
170    app.out_protocol.to_parent(None, cls, inst, parent, cls.get_namespace(),
171                                                                  root_tag_name)
172
173    if no_namespace:
174        _dig(parent)
175
176    etree.cleanup_namespaces(parent)
177
178    return parent[0]
179
180
181def get_xml_as_object_polymorphic(elt, cls):
182    """Returns a native :class:`spyne.model.complex.ComplexModel` child from an
183    ElementTree representation of the same class.
184
185    :param elt: The xml document to be deserialized.
186    :param cls: The class the xml document represents.
187    """
188
189    tns = cls.get_namespace()
190    if tns is None:
191        raise ValueError("Please set a namespace for %r" % (cls, ))
192
193    class _DummyService(ServiceBase):
194        @srpc(cls)
195        def f(_):
196            pass
197
198    app = Application([_DummyService], tns=tns,
199                                      in_protocol=XmlDocument(polymorphic=True))
200
201    unregister_application(app)
202
203    return app.in_protocol.from_element(FakeContext(app=app), cls, elt)
204
205
206def get_object_as_xml_cloth(inst, cls=None, no_namespace=False, encoding='utf8'):
207    """Returns an ElementTree representation of a
208    :class:`spyne.model.complex.ComplexModel` subclass.
209
210    :param inst: The instance of the class to be serialized.
211    :param cls: The class to be serialized. Optional.
212    :param root_tag_name: The root tag string to use. Defaults to the output of
213        ``value.__class__.get_type_name_ns()``.
214    :param no_namespace: When true, namespace information is discarded.
215    """
216
217    if cls is None:
218        cls = inst.__class__
219
220    if cls.get_namespace() is None and no_namespace is None:
221        no_namespace = True
222
223    if no_namespace is None:
224        no_namespace = False
225
226    ostr = BytesIO()
227    xml_cloth = XmlCloth(use_ns=(not no_namespace))
228    ctx = FakeContext()
229    with etree.xmlfile(ostr, encoding=encoding) as xf:
230        ctx.outprot_ctx.doctype_written = False
231        ctx.protocol.prot_stack = tlist([], ProtocolMixin)
232        tn = cls.get_type_name()
233        ret = xml_cloth.subserialize(ctx, cls, inst, xf, tn)
234
235        assert not isgenerator(ret)
236
237    return ostr.getvalue()
238
239
240def get_xml_as_object(elt, cls):
241    """Returns a native :class:`spyne.model.complex.ComplexModel` child from an
242    ElementTree representation of the same class.
243
244    :param elt: The xml document to be deserialized.
245    :param cls: The class the xml document represents.
246    """
247    return _xml_object.from_element(None, cls, elt)
248
249
250def parse_schema_string(s, files={}, repr_=Thier_repr(with_ns=False),
251                                                         skip_errors=False):
252    """Parses a schema string and returns a _Schema object.
253
254    :param s: The string or bytes object that contains the schema document.
255    :param files: A dict that maps namespaces to path to schema files that
256        contain the schema document for those namespaces.
257    :param repr_: A callable that functions as `repr`.
258    :param skip_errors: Skip parsing errors and return a partial schema.
259        See debug log for details.
260
261    :return: :class:`spyne.interface.xml_schema.parser._Schema` instance.
262    """
263
264    elt = etree.fromstring(s, parser=PARSER)
265    return XmlSchemaParser(files, repr_=repr_,
266                            skip_errors=skip_errors).parse_schema(elt)
267
268
269def parse_schema_element(elt, files={}, repr_=Thier_repr(with_ns=False),
270                                                         skip_errors=False):
271    """Parses a `<xs:schema>` element and returns a _Schema object.
272
273    :param elt: The `<xs:schema>` element, an lxml.etree._Element instance.
274    :param files: A dict that maps namespaces to path to schema files that
275        contain the schema document for those namespaces.
276    :param repr_: A callable that functions as `repr`.
277    :param skip_errors: Skip parsing errors and return a partial schema.
278        See debug log for details.
279
280    :return: :class:`spyne.interface.xml_schema.parser._Schema` instance.
281    """
282
283    return XmlSchemaParser(files, repr_=repr_,
284                            skip_errors=skip_errors).parse_schema(elt)
285
286
287def parse_schema_file(file_name, files=None, repr_=Thier_repr(with_ns=False),
288                                                         skip_errors=False):
289    """Parses a schema file and returns a _Schema object. Schema files typically
290    have the `*.xsd` extension.
291
292    :param file_name: The path to the file that contains the schema document
293        to be parsed.
294    :param files: A dict that maps namespaces to path to schema files that
295        contain the schema document for those namespaces.
296    :param repr_: A callable that functions as `repr`.
297    :param skip_errors: Skip parsing errors and return a partial schema.
298        See debug log for details.
299
300    :return: :class:`spyne.interface.xml_schema.parser._Schema` instance.
301    """
302
303    if files is None:
304        files = dict()
305
306    elt = etree.fromstring(open(file_name, 'rb').read(), parser=PARSER)
307    wd = abspath(dirname(file_name))
308    return XmlSchemaParser(files, wd, repr_=repr_,
309                            skip_errors=skip_errors).parse_schema(elt)
310