You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
398 lines
14 KiB
398 lines
14 KiB
|
|
#
|
|
# spyne - Copyright (C) Spyne contributors.
|
|
#
|
|
# This library is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
# License as published by the Free Software Foundation; either
|
|
# version 2.1 of the License, or (at your option) any later version.
|
|
#
|
|
# This library 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
|
|
# Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
# License along with this library; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
|
#
|
|
|
|
"""The ``spyne.interface.xml_schema.model`` module contains type-specific logic
|
|
for schema generation."""
|
|
|
|
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
from decimal import Decimal as D
|
|
from collections import deque, defaultdict
|
|
|
|
from lxml import etree
|
|
|
|
from spyne.model import ModelBase, XmlAttribute, AnyXml, Unicode, XmlData, \
|
|
Decimal, Integer
|
|
from spyne.const.xml import NS_XSD, XSD
|
|
from spyne.util import memoize
|
|
from spyne.util.cdict import cdict
|
|
from spyne.util.etreeconv import dict_to_etree
|
|
from spyne.util.six import string_types
|
|
from spyne.protocol.xml import XmlDocument
|
|
_prot = XmlDocument()
|
|
|
|
# In Xml Schema, some customizations do not need a class to be extended -- they
|
|
# are specified in-line in the parent class definition, like nullable or
|
|
# min_occurs. The dict below contains list of parameters that do warrant a
|
|
# proper subclass definition for each type. This must be updated as the Xml
|
|
# Schema implementation makes progress.
|
|
ATTR_NAMES = cdict({
|
|
ModelBase: set(['values']),
|
|
Decimal: set(['pattern', 'gt', 'ge', 'lt', 'le', 'values', 'total_digits',
|
|
'fraction_digits']),
|
|
Integer: set(['pattern', 'gt', 'ge', 'lt', 'le', 'values', 'total_digits']),
|
|
Unicode: set(['values', 'min_len', 'max_len', 'pattern']),
|
|
})
|
|
|
|
def xml_attribute_add(cls, name, element, document):
|
|
element.set('name', name)
|
|
element.set('type', cls.type.get_type_name_ns(document.interface))
|
|
|
|
if cls._use is not None:
|
|
element.set('use', cls._use)
|
|
|
|
d = cls.type.Attributes.default
|
|
|
|
if d is not None:
|
|
element.set('default', _prot.to_unicode(cls.type, d))
|
|
|
|
|
|
def _check_extension_attrs(cls):
|
|
"""Make sure only customizations that need a restriction tag generate one"""
|
|
|
|
extends = cls.__extends__
|
|
|
|
eattrs = extends.Attributes
|
|
cattrs = cls.Attributes
|
|
|
|
ckeys = set([k for k in vars(cls.Attributes) if not k.startswith('_')])
|
|
ekeys = set([k for k in vars(extends.Attributes) if not k.startswith('_')])
|
|
|
|
# get the attributes different from the parent class
|
|
diff = set()
|
|
for k in (ckeys | ekeys):
|
|
if getattr(eattrs, k, None) != getattr(cattrs, k, None):
|
|
diff.add(k)
|
|
|
|
# compare them with what comes from ATTR_NAMES
|
|
attr_names = ATTR_NAMES[cls]
|
|
retval = None
|
|
while extends is not None:
|
|
retval = extends
|
|
if len(diff & attr_names) > 0:
|
|
return extends
|
|
extends = extends.__extends__
|
|
|
|
return retval
|
|
|
|
# noinspection PyDefaultArgument
|
|
def simple_get_restriction_tag(document, cls):
|
|
extends = _check_extension_attrs(cls)
|
|
if extends is None:
|
|
return
|
|
|
|
simple_type = etree.Element(XSD('simpleType'))
|
|
|
|
simple_type.set('name', cls.get_type_name())
|
|
document.add_simple_type(cls, simple_type)
|
|
|
|
restriction = etree.SubElement(simple_type, XSD('restriction'))
|
|
restriction.set('base', extends.get_type_name_ns(document.interface))
|
|
|
|
for v in cls.Attributes.values:
|
|
enumeration = etree.SubElement(restriction, XSD('enumeration'))
|
|
enumeration.set('value', XmlDocument().to_unicode(cls, v))
|
|
|
|
return restriction
|
|
|
|
|
|
def simple_add(document, cls, tags):
|
|
if not cls.is_default(cls):
|
|
document.get_restriction_tag(cls)
|
|
|
|
def byte_array_add(document, cls, tags):
|
|
simple_add(document, cls, tags)
|
|
|
|
|
|
def complex_add(document, cls, tags):
|
|
complex_type = etree.Element(XSD('complexType'))
|
|
complex_type.set('name', cls.get_type_name())
|
|
|
|
doc_text = cls.get_documentation()
|
|
if doc_text or cls.Annotations.appinfo is not None:
|
|
annotation = etree.SubElement(complex_type, XSD('annotation'))
|
|
if doc_text:
|
|
doc = etree.SubElement(annotation, XSD('documentation'))
|
|
doc.text = doc_text
|
|
|
|
_ai = cls.Annotations.appinfo
|
|
if _ai is not None:
|
|
appinfo = etree.SubElement(annotation, XSD('appinfo'))
|
|
if isinstance(_ai, dict):
|
|
dict_to_etree(_ai, appinfo)
|
|
|
|
elif isinstance(_ai, string_types):
|
|
appinfo.text = _ai
|
|
|
|
elif isinstance(_ai, etree._Element):
|
|
appinfo.append(_ai)
|
|
|
|
else:
|
|
from spyne.util.xml import get_object_as_xml
|
|
|
|
appinfo.append(get_object_as_xml(_ai))
|
|
|
|
sequence_parent = complex_type
|
|
extends = getattr(cls, '__extends__', None)
|
|
|
|
type_info = cls._type_info
|
|
if extends is not None:
|
|
if (extends.get_type_name() == cls.get_type_name() and
|
|
extends.get_namespace() == cls.get_namespace()):
|
|
raise Exception("%r can't extend %r because they are both '{%s}%s'"
|
|
% (cls, extends, cls.get_namespace(), cls.get_type_name()))
|
|
|
|
if extends.Attributes.exc_interface:
|
|
# If the parent class is private, it won't be in the schema, so we
|
|
# need to act as if its attributes are part of cls as well.
|
|
type_info = cls.get_simple_type_info(cls)
|
|
|
|
else:
|
|
complex_content = etree.SubElement(complex_type,
|
|
XSD('complexContent'))
|
|
extension = etree.SubElement(complex_content, XSD('extension'))
|
|
extension.set('base', extends.get_type_name_ns(document.interface))
|
|
sequence_parent = extension
|
|
|
|
if cls.Attributes._xml_tag_body_as is not None:
|
|
for xtba_key, xtba_type in cls.Attributes._xml_tag_body_as:
|
|
_sc = etree.SubElement(sequence_parent, XSD('simpleContent'))
|
|
xtba_ext = etree.SubElement(_sc, XSD('extension'))
|
|
xtba_ext.attrib['base'] = xtba_type.type.get_type_name_ns(
|
|
document.interface)
|
|
|
|
sequence = etree.Element(XSD('sequence'))
|
|
|
|
deferred = deque()
|
|
choice_tags = defaultdict(lambda: etree.Element(XSD('choice')))
|
|
|
|
for k, v in type_info.items():
|
|
assert isinstance(k, string_types)
|
|
assert issubclass(v, ModelBase)
|
|
|
|
a = v.Attributes
|
|
if a.exc_interface:
|
|
continue
|
|
|
|
if issubclass(v, XmlData):
|
|
continue
|
|
|
|
if issubclass(v, XmlAttribute):
|
|
deferred.append((k,v))
|
|
continue
|
|
|
|
document.add(v, tags)
|
|
|
|
name = a.sub_name
|
|
if name is None:
|
|
name = k
|
|
#ns = a.sub_ns
|
|
#if ns is not None:
|
|
# name = "{%s}%s" % (ns, name)
|
|
|
|
type_name_ns = v.get_type_name_ns(document.interface)
|
|
if v.__extends__ is not None and v.__orig__ is not None and \
|
|
_check_extension_attrs(v) is None:
|
|
type_name_ns = v.__orig__.get_type_name_ns(document.interface)
|
|
|
|
member = etree.Element(a.schema_tag)
|
|
if a.schema_tag == XSD('element'):
|
|
member.set('name', name)
|
|
member.set('type', type_name_ns)
|
|
|
|
elif a.schema_tag == XSD('any') and issubclass(v, AnyXml):
|
|
if a.namespace is not None:
|
|
member.set('namespace', a.namespace)
|
|
if a.process_contents is not None:
|
|
member.set('processContents', a.process_contents)
|
|
|
|
else:
|
|
raise ValueError("Unhandled schema_tag / type combination. %r %r"
|
|
% (v, a.schema_tag))
|
|
|
|
if a.min_occurs != 1: # 1 is the xml schema default
|
|
member.set('minOccurs', str(a.min_occurs))
|
|
|
|
if a.max_occurs != 1: # 1 is the xml schema default
|
|
val = a.max_occurs
|
|
if val in (D('inf'), float('inf')):
|
|
val = 'unbounded'
|
|
else:
|
|
val = str(val)
|
|
|
|
member.set('maxOccurs', val)
|
|
|
|
if a.default is not None:
|
|
member.set('default', _prot.to_unicode(v, a.default))
|
|
|
|
if bool(a.nillable) != False: # False is the xml schema default
|
|
member.set('nillable', 'true')
|
|
|
|
v_doc_text = v.get_documentation()
|
|
if v_doc_text:
|
|
# Doesn't support multi-language documentation
|
|
annotation = etree.SubElement(member, XSD('annotation'))
|
|
doc = etree.SubElement(annotation, XSD('documentation'))
|
|
doc.text = v_doc_text
|
|
|
|
if a.xml_choice_group is None:
|
|
sequence.append(member)
|
|
else:
|
|
choice_tags[a.xml_choice_group].append(member)
|
|
|
|
sequence.extend(choice_tags.values())
|
|
|
|
if len(sequence) > 0:
|
|
sequence_parent.append(sequence)
|
|
|
|
_ext_elements = dict()
|
|
for k,v in deferred:
|
|
attribute = etree.Element(XSD('attribute'))
|
|
xml_attribute_add(v, k, attribute, document)
|
|
|
|
if cls.Attributes._xml_tag_body_as is None:
|
|
sequence_parent.append(attribute)
|
|
else:
|
|
xtba_ext.append(attribute)
|
|
|
|
document.add_complex_type(cls, complex_type)
|
|
|
|
# simple node
|
|
complex_type_name = cls.Attributes.sub_name or cls.get_type_name()
|
|
element = etree.Element(XSD('element'))
|
|
element.set('name', complex_type_name)
|
|
element.set('type', cls.get_type_name_ns(document.interface))
|
|
|
|
document.add_element(cls, element)
|
|
|
|
|
|
def enum_add(document, cls, tags):
|
|
simple_type = etree.Element(XSD('simpleType'))
|
|
simple_type.set('name', cls.get_type_name())
|
|
|
|
restriction = etree.SubElement(simple_type, XSD('restriction'))
|
|
restriction.set('base', '%s:string' %
|
|
document.interface.get_namespace_prefix(NS_XSD))
|
|
|
|
for v in cls.__values__:
|
|
enumeration = etree.SubElement(restriction, XSD('enumeration'))
|
|
enumeration.set('value', v)
|
|
|
|
document.add_simple_type(cls, simple_type)
|
|
|
|
fault_add = complex_add
|
|
|
|
|
|
def unicode_get_restriction_tag(document, cls):
|
|
restriction = simple_get_restriction_tag(document, cls)
|
|
if restriction is None:
|
|
return
|
|
|
|
# length
|
|
if cls.Attributes.min_len == cls.Attributes.max_len:
|
|
length = etree.SubElement(restriction, XSD('length'))
|
|
length.set('value', str(cls.Attributes.min_len))
|
|
|
|
else:
|
|
if cls.Attributes.min_len != Unicode.Attributes.min_len:
|
|
min_l = etree.SubElement(restriction, XSD('minLength'))
|
|
min_l.set('value', str(cls.Attributes.min_len))
|
|
|
|
if cls.Attributes.max_len != Unicode.Attributes.max_len:
|
|
max_l = etree.SubElement(restriction, XSD('maxLength'))
|
|
max_l.set('value', str(cls.Attributes.max_len))
|
|
|
|
# pattern
|
|
if cls.Attributes.pattern != Unicode.Attributes.pattern:
|
|
pattern = etree.SubElement(restriction, XSD('pattern'))
|
|
pattern.set('value', cls.Attributes.pattern)
|
|
|
|
return restriction
|
|
|
|
|
|
prot = XmlDocument()
|
|
|
|
|
|
def Tget_range_restriction_tag(T):
|
|
"""The get_range_restriction template function. Takes a primitive, returns
|
|
a function that generates range restriction tags.
|
|
"""
|
|
|
|
from spyne.model.primitive import Decimal
|
|
from spyne.model.primitive import Integer
|
|
|
|
if issubclass(T, Decimal):
|
|
def _get_float_restrictions(prot, restriction, cls):
|
|
if cls.Attributes.fraction_digits != T.Attributes.fraction_digits:
|
|
elt = etree.SubElement(restriction, XSD('fractionDigits'))
|
|
elt.set('value', prot.to_unicode(cls,
|
|
cls.Attributes.fraction_digits))
|
|
|
|
def _get_integer_restrictions(prot, restriction, cls):
|
|
if cls.Attributes.total_digits != T.Attributes.total_digits:
|
|
elt = etree.SubElement(restriction, XSD('totalDigits'))
|
|
elt.set('value', prot.to_unicode(cls,
|
|
cls.Attributes.total_digits))
|
|
|
|
if issubclass(T, Integer):
|
|
def _get_additional_restrictions(prot, restriction, cls):
|
|
_get_integer_restrictions(prot, restriction, cls)
|
|
|
|
else:
|
|
def _get_additional_restrictions(prot, restriction, cls):
|
|
_get_integer_restrictions(prot, restriction, cls)
|
|
_get_float_restrictions(prot, restriction, cls)
|
|
|
|
else:
|
|
def _get_additional_restrictions(prot, restriction, cls):
|
|
pass
|
|
|
|
def _get_range_restriction_tag(document, cls):
|
|
restriction = simple_get_restriction_tag(document, cls)
|
|
if restriction is None:
|
|
return
|
|
|
|
if cls.Attributes.gt != T.Attributes.gt:
|
|
elt = etree.SubElement(restriction, XSD('minExclusive'))
|
|
elt.set('value', prot.to_unicode(cls, cls.Attributes.gt))
|
|
|
|
if cls.Attributes.ge != T.Attributes.ge:
|
|
elt = etree.SubElement(restriction, XSD('minInclusive'))
|
|
elt.set('value', prot.to_unicode(cls, cls.Attributes.ge))
|
|
|
|
if cls.Attributes.lt != T.Attributes.lt:
|
|
elt = etree.SubElement(restriction, XSD('maxExclusive'))
|
|
elt.set('value', prot.to_unicode(cls, cls.Attributes.lt))
|
|
|
|
if cls.Attributes.le != T.Attributes.le:
|
|
elt = etree.SubElement(restriction, XSD('maxInclusive'))
|
|
elt.set('value', prot.to_unicode(cls, cls.Attributes.le))
|
|
|
|
if cls.Attributes.pattern != T.Attributes.pattern:
|
|
elt = etree.SubElement(restriction, XSD('pattern'))
|
|
elt.set('value', cls.Attributes.pattern)
|
|
|
|
_get_additional_restrictions(prot, restriction, cls)
|
|
|
|
return restriction
|
|
|
|
return _get_range_restriction_tag
|