# -*- coding: utf-8 -*-
################################################################################
'''
Design Decision Log
-------------------
1. I have decided to not use dews/dexml approach to field description as it
doesn't give good distinction between element and attribute. It is not a
problem when parsing a XML, but it is quite important for rendering and XSD
generation. The new syntax will look like:
tail_number = xsd.Attribute(xsd.String)
flight_number = xsd.Element(xsd.Integer)
which makes this distinction clear.
2. Render will take value/instance as parameter. More obvious would be if
render just rendered current object, but this approach doesn't work with
Python simple types like string. Where you can not call 'x'.render() so
type method render must take a value as a parameter, which may same odd for
complex types.
3. Due to render taking a value as parameter it could be implemented as a
static/class method, but it is not. xsd.Element takes a class or an
instance, but if class was passed it will create an instance - so a
parameter-less constructor is required Reason for that is to keep API
consistent. There are two syntaxes:
a. xsd.Element(xsd.String)
b. xsd.Element(xsd.String(enumeration=['A', 'B']))
because instance if required in case (b) creating it from class in case (a)
makes other methods independent from this two syntaxes.
Notes
-----
For information on XML schema validation:
- http://lxml.de/validation.html#xmlschema
'''
################################################################################
# Imports
import re
from copy import copy
from datetime import datetime
from lxml import etree
from . import iso8601, settings
################################################################################
# Globals
NIL = object()
UNBOUNDED = 'unbounded'
################################################################################
# Classes
[docs]class TypeRegister(object):
'''
Allows tracking user defined class and their names, to be able to resolve
string references e.g. a = xsd.Element('A'). Note that class names must be
unique due to fact that search engine uses just class names.
'''
def __init__(self):
'''
'''
self.types = []
[docs] def add_type(self, clazz):
'''
'''
self.types.append(clazz)
[docs] def find_type(self, typeid):
'''
'''
for clazz in self.types:
if clazz.__name__ == typeid:
return clazz
USER_TYPE_REGISTER = TypeRegister()
[docs]class CallStyle(object):
'''
'''
DOCUMENT = 'document'
RPC = 'RPC'
[docs]class Use(object):
'''
'''
OPTIONAL = 'optional'
REQUIRED = 'required'
PROHIBITED = 'prohibited'
[docs]class Inheritance(object):
'''
'''
RESTRICTION = 'RESTRICTION'
EXTENSION = 'EXTENSION'
[docs]class Indicator(object):
'''
'''
def __init__(self, fields):
'''
'''
self.fields = fields
[docs]class Sequence(Indicator):
'''
'''
pass
[docs]class Choice(Indicator):
'''
'''
pass
[docs]class All(Indicator):
'''
'''
pass
[docs]class Type_PythonType(type):
'''
'''
def __new__(cls, name, bases, attrs):
'''
'''
newcls = super(Type_PythonType, cls).__new__(cls, name, bases, attrs)
USER_TYPE_REGISTER.add_type(newcls)
return newcls
[docs]class Type(object):
'''
Abstract.
'''
__metaclass__ = Type_PythonType
[docs] def accept(self, value):
'''
'''
raise NotImplementedError
[docs] def parse_xmlelement(self, xmlelement):
'''
'''
raise NotImplementedError
[docs] def parsexml(self, xml):
'''
'''
raise NotImplementedError
[docs] def render(self, parent, value):
'''
'''
raise NotImplementedError
[docs]class SimpleType(Type):
'''
Defines an interface for simple types.
'''
[docs] def render(self, parent, value, namespace, elementFormDefault):
'''
'''
parent.text = self.xmlvalue(value)
[docs] def parse_xmlelement(self, xmlelement):
'''
'''
return self.pythonvalue(xmlelement.text)
[docs] def xmlvalue(self, value):
'''
'''
raise NotImplementedError
[docs] def pythonvalue(self, xmlvalue):
'''
'''
raise NotImplementedError
[docs]class String(SimpleType):
'''
'''
enumeration = None # To be defined in child.
pattern = None # To be defined in child.
def __init__(self, enumeration=None, pattern=None):
'''
'''
if enumeration is not None:
self.enumeration = enumeration # Override static value
if pattern is not None:
self.pattern = pattern # Override static value
[docs] def accept(self, value):
'''
'''
if value is None:
return value
if not isinstance(value, basestring):
raise ValueError("Value '%s' for class '%s'." % (unicode(value), self.__class__.__name__))
if self.pattern:
cp = re.compile(self.pattern)
if not cp.match(value):
raise ValueError("Value '%s' doesn't match pattern '%s'" % (value, self.pattern))
if self.enumeration:
if not (value in self.enumeration):
raise ValueError("Value '%s' not in list %s." % (unicode(value), self.enumeration))
return value
[docs] def xmlvalue(self, value):
'''
'''
return value
[docs] def pythonvalue(self, xmlvalue):
'''
'''
return xmlvalue
[docs]class Boolean(SimpleType):
'''
'''
[docs] def accept(self, value):
'''
'''
if value in [True, False, None]:
return value
else:
raise ValueError("Value '%s' for class '%s'." % (str(value), self.__class__.__name__))
[docs] def xmlvalue(self, value):
'''
'''
if value == False:
return 'false'
elif value == True:
return 'true'
elif value is None:
return 'nil'
else:
raise ValueError("Value '%s' for class '%s'." % (str(value), self.__class__.__name__))
[docs] def pythonvalue(self, value):
'''
'''
if value == 'false':
return False
elif value == 'true':
return True
elif value == 'nil' or value is None:
return None
else:
raise ValueError("Boolean value error - %s" % value)
[docs]class DateTime(SimpleType):
'''
Example text value: 2001-10-26T21:32:52
'''
FORMAT = '%Y-%m-%dT%H:%M:%S%z'
[docs] def accept(self, value):
'''
'''
if value is None:
return None
elif isinstance(value, datetime):
return value
elif isinstance(value, basestring):
return iso8601.parse_date(value)
raise ValueError("Incorrect type value '%s' for Datetime field." % value)
[docs] def xmlvalue(self, value):
'''
'''
if value is None:
return 'nil'
else:
return value.strftime(self.FORMAT)
[docs] def pythonvalue(self, value):
'''
'''
if value is None or value == 'nil':
return None
else:
return iso8601.parse_date(value)
[docs]class Decimal(SimpleType):
'''
'''
def __init__(self, enumeration=None, fractionDigits=None, maxExclusive=None,
maxInclusive=None, minExclusive=None, minInclusive=None,
pattern=None, totalDigits=None):
'''
'''
self.enumeration = enumeration
self.fractionDigits = fractionDigits
self.maxExclusive = maxExclusive
self.maxInclusive = maxInclusive
self.minExclusive = minExclusive
self.minInclusive = minInclusive
self.pattern = pattern
self.totalDigits = totalDigits
def _check_restrictions(self, value):
'''
'''
if self.enumeration is not None and value not in self.enumeration:
raise ValueError('%s not in enumeration %s' % (value, self.enumeration))
if self.fractionDigits is not None:
strvalue = str(value)
if self.fractionDigits == 0:
if '.' in strvalue:
raise ValueError('Wrong fraction digits for value %s allowed %s' % (strvalue, self.fractionDigits))
else:
if '.' not in strvalue:
raise ValueError('Wrong fraction digits for value %s allowed %s' % (strvalue, self.fractionDigits))
fr = strvalue.split('.')[1]
if len(fr) != self.fractionDigits:
raise ValueError('Wrong fraction digits for value %s allowed %s' % (strvalue, self.fractionDigits))
if self.maxExclusive is not None and value >= self.maxExclusive:
raise ValueError('Value %s greater or equal to maxExclusive %s' % (value, self.maxExclusive))
if self.maxInclusive is not None and value > self.maxInclusive:
raise ValueError('Value %s greater than maxInclusive %s' % (value, self.maxInclusive))
if self.minExclusive is not None and value <= self.minExclusive:
raise ValueError('Value %s smaller or equal to minExclusive %s' % (value, self.minExclusive))
if self.minInclusive is not None and value < self.minInclusive:
raise ValueError('Value %s smaller than minInclusive %s' % (value, self.minInclusive))
if self.pattern is not None:
compiled_pattern = re.compile(self.pattern)
if not compiled_pattern.match(str(value)):
raise ValueError('Value %s doesn\'t match pattern %s.' % (value, self.pattern))
if self.totalDigits is not None:
strvalue = str(value)
l = len(strvalue)
if '.' in strvalue:
l = l - 1
if l > self.totalDigits:
raise ValueError('Number of total digits of %s is bigger than %s.' % (strvalue, self.totalDigits))
return value
[docs] def accept(self, value):
'''
'''
if value is None:
return None
elif isinstance(value, int) or isinstance(value, long) or isinstance(value, float):
pass # value is just value
elif isinstance(value, basestring):
value = float(value)
else:
raise ValueError("Incorrect value '%s' for Decimal field." % value)
self._check_restrictions(value)
return value
[docs] def xmlvalue(self, value):
'''
'''
return str(value)
[docs] def pythonvalue(self, xmlvalue):
'''
'''
if xmlvalue == 'nil':
return None
else:
return self.accept(xmlvalue)
[docs]class Double(Decimal):
'''
'''
def __init__(self, enumeration=None, maxExclusive=None, maxInclusive=None,
minExclusive=None, minInclusive=None, pattern=None):
'''
'''
super(Double, self).__init__(
enumeration=enumeration, maxExclusive=maxExclusive,
maxInclusive=maxInclusive, minExclusive=minExclusive,
minInclusive=minInclusive, pattern=pattern)
[docs]class Float(Decimal):
'''
'''
def __init__(self, enumeration=None, maxExclusive=None, maxInclusive=None,
minExclusive=None, minInclusive=None, pattern=None):
'''
'''
super(Float, self).__init__(
enumeration=enumeration, maxExclusive=maxExclusive,
maxInclusive=maxInclusive, minExclusive=minExclusive,
minInclusive=minInclusive, pattern=pattern)
[docs]class Integer(Decimal):
'''
'''
def __init__(self, enumeration=None, maxExclusive=None,
maxInclusive=None, minExclusive=None, minInclusive=None,
pattern=None, totalDigits=None):
'''
'''
super(Integer, self).__init__(enumeration=enumeration, fractionDigits=0, maxExclusive=maxExclusive,
maxInclusive=maxInclusive, minExclusive=minExclusive, minInclusive=minInclusive,
pattern=pattern, totalDigits=totalDigits)
[docs] def accept(self, value):
'''
'''
if value is None:
return None
elif isinstance(value, int) or isinstance(value, long):
pass # value is just value continue.
elif isinstance(value, basestring):
value = int(value)
else:
raise ValueError("Incorrect value '%s' for Decimal field." % value)
self._check_restrictions(value)
return value
[docs]class Long(Integer):
'''
'''
def __init__(self, enumeration=None, maxExclusive=None,
maxInclusive=9223372036854775807, minExclusive=None, minInclusive=-9223372036854775808,
pattern=None, totalDigits=None):
'''
'''
super(Integer, self).__init__(enumeration=enumeration, fractionDigits=0, maxExclusive=maxExclusive,
maxInclusive=maxInclusive, minExclusive=minExclusive, minInclusive=minInclusive,
pattern=pattern, totalDigits=totalDigits)
[docs]class Int(Long):
'''
'''
def __init__(self, enumeration=None, maxExclusive=None,
maxInclusive=2147483647, minExclusive=None, minInclusive=-2147483648,
pattern=None, totalDigits=None):
'''
'''
super(Integer, self).__init__(enumeration=enumeration, fractionDigits=0, maxExclusive=maxExclusive,
maxInclusive=maxInclusive, minExclusive=minExclusive, minInclusive=minInclusive,
pattern=pattern, totalDigits=totalDigits)
[docs]class Element(object):
'''
Basic building block, represents a XML element that can appear one or zero
times in XML that should be rendered as subelement e.g.
<aircraft><tail_number>LN-KKY</tail_number></aircraft> Tail number is
element. For elements that can appear multiple times use ListElement.
'''
_creation_counter = 0
def __init__(self, _type, minOccurs=1, tagname=None, nillable=False,
default=None, namespace=None):
'''
:param _type: Class or instance of class that inherits from Type,
usually a child of SimpleType from xsd package, or user
defined class that inherits from ComplexType.
:param minOccurs: int, how many times this object can appear in valid
XML can be 0 or 1. See: difference between Element
and ListElement.
:param tagname: str, name of tag when different to field declared in
ComplexType, important when tag name is python reserved
work e.g. import
:param nillable: bool, is object nilable.
'''
if not minOccurs in [0, 1]:
raise 'minOccurs for Element can by only 0 or 1, use ListElement instead.'
self._creation_number = Element._creation_counter
Element._creation_counter += 1
self._passed_type = _type
self._type = None # Will be evaluated when needed from _passed_type
self._minOccurs = minOccurs
self.tagname = tagname
self.default = default
self.nillable = nillable
self.namespace = namespace
def _evaluate_type(self):
'''
'''
if self._type is None:
if isinstance(self._passed_type, basestring):
self._passed_type = USER_TYPE_REGISTER.find_type(self._passed_type)
if isinstance(self._passed_type, Type):
self._type = self._passed_type
else:
self._type = self._passed_type()
[docs] def empty_value(self):
'''
Empty value methods is used when new object is constructed for field
initialization in most cases this should be None, but for lists and
other types of aggregates this should by an empty aggregate.
'''
return self.default
[docs] def accept(self, value):
'''
Checks is the value correct from type defined in constructions.
'''
self._evaluate_type()
if value == NIL:
if self.nillable:
return NIL
else:
raise ValueError('Nil value for not nillable element.')
else:
return self._type.accept(value)
[docs] def render(self, parent, field_name, value, namespace=None, elementFormDefault=None):
'''
'''
self._evaluate_type()
if value is None:
return
if self.namespace is not None:
namespace = self.namespace
if namespace is not None and elementFormDefault == ElementFormDefault.QUALIFIED:
field_name = '{%s}%s' % (namespace, field_name)
xmlelement = etree.Element(field_name)
if value == NIL:
xmlelement.set('{http://www.w3.org/2001/XMLSchema-instance}nil', 'true')
else:
self._type.render(xmlelement, value, namespace, elementFormDefault)
parent.append(xmlelement)
[docs] def parse(self, instance, field_name, xmlelement):
'''
'''
self._evaluate_type()
if xmlelement.get('{http://www.w3.org/2001/XMLSchema-instance}nil') == 'true':
value = NIL
else:
value = self._type.parse_xmlelement(xmlelement)
setattr(instance, field_name, value)
def __repr__(self):
'''
'''
if isinstance(self._type, basestring):
return '%s<%s>' % (self.__class__.__name__, self._type)
else:
return '%s<%s>' % (self.__class__.__name__, self._type.__class__.__name__)
[docs]class ClassNamedElement(Element):
'''
Use this element when tagname should be based on class name in rendering
time.
'''
def __init__(self, _type, minOccurs=1, nilable=False):
'''
'''
super(ClassNamedElement, self).__init__(_type, minOccurs, None, nilable)
[docs] def render(self, parent, field_name, value, namespace=None, elementFormDefault=None):
'''
'''
if value is None:
return
tagname = value.name
value = value.value
if value is None:
return
namespace = value.SCHEMA.targetNamespace
if namespace:
tagname = '{%s}%s' % (namespace, tagname)
xmlelement = etree.Element(tagname)
self._type.render(xmlelement, value, namespace=namespace,
elementFormDefault=value.SCHEMA.elementFormDefault)
parent.append(xmlelement)
[docs]class Attribute(Element):
'''
Represents a field that is a XML attribute. e.g.
<person name="Jhon" surname="Dough">
<job>Programmer<job>
</person>
name and surname are attributes. Attribute type can be only simple types.
'''
def __init__(self, type_clazz, use=Use.REQUIRED, tagname=None, nillable=False,
default=None):
'''
:param type_clazz: Only simple tapes are accepted: String, Integer etc.
'''
minOccurs = 1 if use == Use.REQUIRED else 0
super(Attribute, self).__init__(type_clazz, tagname=tagname, minOccurs=minOccurs)
self.nillable = nillable
self.use = use
self.default = default
[docs] def render(self, parent, field_name, value, namespace=None, elementFormDefault=None):
'''
'''
self._evaluate_type()
if value is None:
if self._minOccurs:
raise ValueError('Value None is not acceptable for required field.')
else:
return
elif value == NIL:
if self.nillable:
xmlvalue = 'nil'
else:
raise ValueError('Nil value for not nillable Attribute.')
else:
xmlvalue = self._type.xmlvalue(value)
parent.set(field_name, xmlvalue)
[docs] def parse(self, instance, field_name, xmlelement):
'''
'''
self._evaluate_type()
xmlvalue = xmlelement.get(field_name)
if xmlvalue is None:
xmlvalue = self.default
value = self._type.pythonvalue(xmlvalue)
setattr(instance, field_name, value)
[docs]class Ref(Element):
'''
References are not fields, they point to type that has them - usually groups.
With Ref fields will be rendered directly into parent object. e.g.
class Person(xsd.Group):
name = xsd.Element(xsd.String)
surname = xsd.Element(xsd.String)
class Job(xsd.ComplexType):
title = xsd.Element(xsd.String)
person = xsd.Ref(Person)
The valid XML will be:
<job>
<title>Programmer</title>
<name>An</name>
<surname>Brown</surname>
</job>
Note that name and surname are not wrapped with <person> tag.
'''
[docs] def empty_value(self):
'''
'''
self._evaluate_type()
return copy(self._type)
[docs] def render(self, parent, field_name, value, namespace=None, elementFormDefault=None):
'''
'''
if value is None:
if self._required:
raise ValueError('Value None is not acceptable for required field.')
else:
return
self._type.render(parent, value, namespace, elementFormDefault)
[docs]class Content(Ref):
'''
Direct access to element.text. Note that <> will be escaped.
'''
[docs] def empty_value(self):
'''
'''
return None
[docs]class ListElement(Element):
'''
Tag element that can appear many times in valid XML. e.g.
<flight>
<aircraft>G-ABCD</aircraft>
<passenger>John Backus</passenger>
<passenger>Kent Beck</passenger>
<passenger>Larry Breed</passenger>
</flight>
passenger is an example of ListElement, the definition would look:
passengers = xsd.ListElement(xsd.String, "passenger")
Note that tag name is required for this field, as the field name should
be in plural form, and tag usually is not.
'''
def __init__(self, clazz, tagname, minOccurs=None, maxOccurs=None, nillable=False):
'''
'''
super(ListElement, self).__init__(clazz, tagname=tagname, nillable=nillable)
self._maxOccurs = maxOccurs
self._minOccurs = minOccurs
[docs] def accept(self, value):
'''
'''
return value
[docs] def empty_value(this):
'''
'''
class TypedList(list):
'''
'''
def append(self, value):
'''
'''
this._evaluate_type()
if value == NIL:
if this.nillable:
accepted_value = NIL
else:
raise ValueError("Nil value in not nillable list.")
else:
accepted_value = this._type.accept(value)
if this._maxOccurs is not None and this._maxOccurs != UNBOUNDED:
if (len(self) + 1) > this._maxOccurs:
raise ValueError("Number of items in list %s is would be bigger than maxOccurs %s" % (len(self), this._maxOccurs))
super(TypedList, self).append(accepted_value)
return TypedList()
[docs] def render(self, parent, field_name, value, namespace=None, elementFormDefault=None):
'''
'''
self._evaluate_type()
items = value # The value must be list of items.
if self._minOccurs and len(items) < self._minOccurs:
raise ValueError('For %s minOccurs=%d but list length %d.' % (field_name, self._minOccurs, len(items)))
if self._maxOccurs and len(items) > self._maxOccurs:
raise ValueError('For %s maxOccurs=%d but list length %d.' % (field_name, self._maxOccurs))
if self.namespace is not None:
namespace = self.namespace
if namespace is not None and elementFormDefault == ElementFormDefault.QUALIFIED:
tagname = '{%s}%s' % (namespace, self.tagname)
else:
tagname = self.tagname
for item in items:
xmlelement = etree.Element(tagname)
if item == NIL:
xmlelement.set('{http://www.w3.org/2001/XMLSchema-instance}nil', 'true')
else:
self._type.render(xmlelement, item, namespace, elementFormDefault)
parent.append(xmlelement)
[docs] def parse(self, instance, field_name, xmlelement):
'''
'''
self._evaluate_type()
if xmlelement.get('{http://www.w3.org/2001/XMLSchema-instance}nil'):
value = NIL
else:
value = self._type.parse_xmlelement(xmlelement)
_list = getattr(instance, field_name)
_list.append(value)
[docs]class Complex_PythonType(Type_PythonType):
'''
Python type for ComplexType, builds _meta object for every class that
inherit from ComplexType.
'''
def __new__(cls, name, bases, attrs):
'''
'''
newcls = super(Complex_PythonType, cls).__new__(cls, name, bases, attrs)
if name != 'Complex':
newcls._meta = ComplexTypeMetaInfo(newcls)
return newcls
[docs]class ComplexType(Type):
'''
Parent for XML elements that have sub-elements.
'''
INDICATOR = Sequence # Indicator see: class Indicators. To be defined in sub-type.
INHERITANCE = None # Type of inheritance see: class Inheritance, to be defined in sub-type.
SCHEMA = None
__metaclass__ = Complex_PythonType
def __new__(cls, *args, **kwargs):
'''
'''
instance = super(ComplexType, cls).__new__(cls)
for field in instance._meta.all:
setattr(instance, field._name, field.empty_value())
return instance
def __init__(self, **kwargs):
'''
'''
for key, value in kwargs.items():
setattr(self, key, value)
def __setattr__(self, attr, value):
'''
'''
if attr == '_xmlelement':
super(ComplexType, self).__setattr__(attr, value)
else:
try:
field = self._find_field(self._meta.all, attr)
super(ComplexType, self).__setattr__(attr, field.accept(value))
except IndexError:
raise AttributeError("Model '%s' doesn't have attribute '%s'." % (self.__class__.__name__, attr))
[docs] def accept(self, value):
'''
Instance methods that validate other instance.
'''
if value is None:
return None
elif isinstance(value, self.__class__):
return value
else:
raise ValueError('Wrong value object type %s for %s.' % (value, self.__class__.__name__))
[docs] def render(self, parent, instance, namespace=None, elementFormDefault=None):
'''
'''
if instance is None:
return None
if self.SCHEMA:
namespace = self.SCHEMA.targetNamespace
for field in instance._meta.all:
field.render(
parent=parent,
field_name=field._name,
value=getattr(instance, field._name),
namespace=namespace,
elementFormDefault=elementFormDefault)
@classmethod
def _find_field(cls, fields, name):
'''
'''
return filter(lambda f: f._name == name, fields)[0]
@classmethod
def _get_field_by_name(cls, fields, field_name):
'''
'''
for field in fields:
if field.tagname == field_name or field._name == field_name:
return field
raise ValueError("Field not found '%s', fields: %s" % (field_name, fields))
@classmethod
def _find_subelement(cls, field, xmlelement):
'''
'''
def gettagns(tag):
'''
Translates tag string in format {namespace} tag to tuple
(namespace, tag).
'''
if tag[0] == '{':
return tag[1:].split('}', 1)
else:
return (None, tag)
subelements = []
for subelement in xmlelement:
if isinstance(subelement, etree._Comment):
continue
ns, tag = gettagns(subelement.tag)
if tag == field._name or tag == field.tagname:
subelements.append(subelement)
return subelements
@classmethod
[docs] def parse_xmlelement(cls, xmlelement):
'''
'''
instance = cls()
instance._xmlelement = xmlelement
for attribute in instance._meta.attributes:
attribute.parse(instance, attribute._name, xmlelement)
for field in instance._meta.fields:
subelements = cls._find_subelement(field, xmlelement)
for subelement in subelements:
field.parse(instance, field._name, subelement)
for group in instance._meta.groups:
group.parse(instance, group._name, xmlelement)
return instance
@classmethod
def __parse_with_validation(cls, xml, schema):
'''
'''
from py2xsd import generate_xsd
schema = generate_xsd(schema)
schemaelement = etree.XMLSchema(schema)
parser = etree.XMLParser(schema=schemaelement)
xmlelement = etree.fromstring(xml, parser)
return xmlelement
@classmethod
[docs] def parsexml(cls, xml, schema=None):
'''
'''
if schema and settings.VALIDATE_ON_PARSE:
xmlelement = cls.__parse_with_validation(xml, schema)
else:
xmlelement = etree.fromstring(xml)
return cls.parse_xmlelement(xmlelement)
[docs] def xml(self, tagname, namespace=None, elementFormDefault=None, schema=None):
'''
'''
if namespace:
tagname = '{%s}%s' % (namespace, tagname)
xmlelement = etree.Element(tagname)
self.render(xmlelement, self, namespace, elementFormDefault)
xml = etree.tostring(xmlelement, pretty_print=True)
if schema and settings.VALIDATE_ON_PARSE:
self.__parse_with_validation(xml, schema)
return xml
@classmethod
def _force_elements_type_evalution(cls):
'''
Allows schema object to force elements type evalution for XSD
generation.
'''
for element in cls._meta.all:
element._evaluate_type()
[docs]class Group(ComplexType):
'''
Parent object for XSD Groups. Marker. Must be use with Ref.
'''
pass
[docs]class AttributeGroup(Group):
'''
Parent object for XSD Attribute Groups. Marker. Must be use with Ref.
'''
pass
[docs]class Document(ComplexType):
'''
Represents whole xml, is expected to have only one field the root.
'''
NAMESPACE = None
[docs] class MockElement(object):
'''
'''
def __init__(self):
'''
'''
self.element = None
[docs] def append(self, element):
'''
'''
self.element = element
[docs] def render(self):
'''
'''
field = self._meta.fields[0] # The only field.
mockelement = Document.MockElement()
instance = getattr(self, field._name)
field.render(mockelement, field._name, instance, self.NAMESPACE)
return etree.tostring(mockelement.element, pretty_print=True)
# TODO: Add schema support.
@classmethod
[docs] def parsexml(cls, xml):
'''
'''
field = cls._meta.fields[0] # The only field.
xmlelement = etree.fromstring(xml)
field.parse(cls, field._name, xmlelement)
[docs]class UnsignedLong(Long):
'''
'''
pass
[docs]class UnsignedInt(Int):
'''
'''
pass
[docs]class List(SimpleType):
'''
'''
pass
[docs]class AnyURI(String):
'''
'''
pass
[docs]class QName(String):
'''
'''
pass
[docs]class NMTOKEN(String):
'''
'''
pass
[docs]class NMTOKENS(String):
'''
'''
pass
[docs]class AnyType(Type):
'''
'''
pass
[docs]class Base64Binary(String):
'''
'''
pass
[docs]class Duration(String):
'''
'''
pass
[docs]class UnsignedShort(Int):
'''
'''
pass
[docs]class UnsignedByte(UnsignedShort):
'''
'''
pass
[docs]class Short(Int):
'''
'''
pass
[docs]class Byte(Short):
'''
'''
pass
[docs]class Schema(object):
'''
Main object for XSD schema. This object is required for XSD and
WSDLgeneration and correct namespaces as it propagates targetNamespace to
all objects. Instance of this is expected to be named Schema.
'''
def __init__(self, targetNamespace, elementFormDefault=ElementFormDefault.UNQUALIFIED,
simpleTypes=[], attributeGroups=[], groups=[], complexTypes=[], elements={},
imports=None, location=None):
'''
:param targetNamespace: string, xsd namespace URL.
:param elementFormDefault: unqualified/qualified Defines should
namespace be used in child elements.
Suggested: qualified. Default: unqualified
as it is default in XSD.
:param simpleTypes: List of objects that extend xsd.SimpleType.
:param attributeGroups: List of objects that extend xsd.AttributeGroup.
:param groups: List of objects that extend xsd.Group.
:param complexTypes: List of complexTypes class.
:param elements: dict of xsd.Elements that are direct schema elements.
'''
self.targetNamespace = targetNamespace
self.elementFormDefault = elementFormDefault
self.simpleTypes = simpleTypes
self.attributeGroups = attributeGroups
self.groups = groups
self.complexTypes = complexTypes
self.elements = elements
self.imports = imports
self.location = location
self.__init_schema(self.simpleTypes)
self.__init_schema(self.groups)
self.__init_schema(self.attributeGroups)
self.__init_schema(self.complexTypes)
for element in self.elements.values():
if isinstance(element._passed_type, ComplexType):
element._passed_type.__class__.SCHEMA = self
self._force_elements_type_evalution(self.complexTypes)
self._force_elements_type_evalution(self.attributeGroups)
self._force_elements_type_evalution(self.groups)
for element in self.elements.values():
element._evaluate_type()
def __init_schema(self, types):
'''
'''
for _type in types:
_type.SCHEMA = self
def _force_elements_type_evalution(self, types):
'''
'''
for t in types:
t._force_elements_type_evalution()
[docs] def get_element_by_name(self, name):
'''
'''
if name in self.elements:
return self.elements[name]
for _import in self.imports:
element = _import.get_element_by_name(name)
if element is not None:
return element
return None
[docs]class Method(object):
'''
Method description. The main information is mapping soapAction and
operationName to function for dispatcher. input and output mapping informs
how and which objects should be created on incoming/outgoing messages.
'''
def __init__(self, operationName, soapAction, input, output, function=None,
inputPartName="body", outputPartName="body", style=CallStyle.DOCUMENT):
'''
:param function: The function that should be called. Required only for server.
'''
self.operationName = operationName
self.soapAction = soapAction
self.input = input
self.output = output
self.function = function
self.inputPartName = inputPartName
self.outputPartName = outputPartName
self.style = style
[docs]class NamedType(ComplexType):
'''
'''
name = Element(String)
value = Element(ComplexType)
def __init__(self, name=None, value=None):
'''
'''
self.name = name
self.value = value
################################################################################
# vim:et:ft=python:nowrap:sts=4:sw=4:ts=4