Add XML data depth limits

- Add module xmlschema.limits for store processing limits
  - Add max_depth optional argument to decode methods
  - Code cleaning for iter_decode() kwargs (elements and groups)
This commit is contained in:
Davide Brunato 2019-10-24 22:13:06 +02:00
parent a374d15805
commit df6eb23516
11 changed files with 209 additions and 124 deletions

View File

@ -526,35 +526,6 @@ For example you can build a schema using a *strict* mode and then decode XML dat
using the *validation* argument setted to 'lax'.
XML entity-based attacks protection
-----------------------------------
The XML data resource loading is protected using the `SafeXMLParser` class, a subclass of
the pure Python version of XMLParser that forbids the use of entities.
The protection is applied both to XSD schemas and to XML data. The usage of this feature is
regulated by the XMLSchema's argument *defuse*.
For default this argument has value *'remote'* that means the protection on XML data is
applied only to data loaded from remote. Other values for this argument can be *'always'*
and *'never'*.
Limit on model groups checking
------------------------------
From release v1.0.11 the model groups of the schemas are checked against restriction violations
and *Unique Particle Attribution* violations.
To avoids XSD model recursion attacks a limit of ``MAX_MODEL_DEPTH = 15`` is set. If this limit
is exceeded an ``XMLSchemaModelDepthError`` is raised, the error is caught and a warning is generated.
If you need to set an higher limit for checking all your groups you can import the library and change
the value in the specific module that processes the model checks:
.. doctest::
>>> import xmlschema
>>> xmlschema.validators.models.MAX_MODEL_DEPTH = 20
Lazy validation
---------------
@ -570,3 +541,53 @@ From release v1.0.14 XSD 1.1 support has been added to the library through the c
:class:`XMLSchema11`. You have to use this class for XSD 1.1 schemas instead the default
class :class:`XMLSchema` that is still linked to XSD 1.0 validator :class:`XMLSchema10`.
From next minor release (v1.1) the default class will become :class:`XMLSchema11`.
XML entity-based attacks protection
...................................
The XML data resource loading is protected using the `SafeXMLParser` class, a subclass of
the pure Python version of XMLParser that forbids the use of entities.
The protection is applied both to XSD schemas and to XML data. The usage of this feature is
regulated by the XMLSchema's argument *defuse*.
For default this argument has value *'remote'* that means the protection on XML data is
applied only to data loaded from remote. Other values for this argument can be *'always'*
and *'never'*.
Processing limits
-----------------
From release v1.0.16 a module has been added in order to group constants that define
processing limits, generally to protect against attacks prepared to exhaust system
resources. These limits usually don't need to be changed, but this possibility has
been left at the module level for situations where a different setting is needed.
Limit on XSD model groups checking
..................................
Model groups of the schemas are checked against restriction violations and *Unique Particle
Attribution* violations. To avoids XSD model recursion attacks a depth limit of 15 levels
is set. If this limit is exceeded an ``XMLSchemaModelDepthError`` is raised, the error is
caught and a warning is generated. If you need to set an higher limit for checking all your
groups you can import the library and change the value of ``MAX_MODEL_DEPTH`` in the limits
module:
.. doctest::
>>> import xmlschema
>>> xmlschema.limits.MAX_MODEL_DEPTH = 20
Limit on XML data depth
.......................
A limit of 9999 on maximum depth is set for XML validation/decoding/encoding to avoid
attacks based on extremely deep XML data. To increase or decrease this limit change the
value of ``MAX_XML_DEPTH`` in the module *limits* after the import of the package:
.. doctest::
>>> import xmlschema
>>> xmlschema.limits.MAX_XML_DEPTH = 1000

View File

@ -8,6 +8,7 @@
#
# @author Davide Brunato <brunato@sissa.it>
#
from . import limits
from .exceptions import XMLSchemaException, XMLSchemaRegexError, XMLSchemaURLError, \
XMLSchemaNamespaceError
from .etree import etree_tostring

21
xmlschema/limits.py Normal file
View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
#
# Copyright (c), 2016-2019, SISSA (International School for Advanced Studies).
# All rights reserved.
# This file is distributed under the terms of the MIT License.
# See the file 'LICENSE' in the root directory of the present
# distribution, or http://opensource.org/licenses/MIT.
#
# @author Davide Brunato <brunato@sissa.it>
#
"""Package protection limits. Values can be changed after import to set different limits."""
MAX_XML_DEPTH = 9999
"""
Maximum depth of XML data. An `XMLSchemaValidationError` is raised if this limit is exceeded.
"""
MAX_MODEL_DEPTH = 15
"""
Maximum XSD model group depth. An `XMLSchemaModelDepthError` is raised if this limit is exceeded.
"""

View File

@ -77,13 +77,33 @@ class TestValidation(XsdValidatorTestCase):
self.assertRaises(XMLSchemaValidationError, xsd_element.decode, source.root, namespaces=namespaces)
# Testing adding 'no_depth' argument
for result in xsd_element.iter_decode(source.root, 'strict', namespaces=namespaces,
source=source, no_depth=True):
source=source, max_depth=1):
del result
self.assertIsNone(xmlschema.validate(self.col_xml_file, lazy=True))
def test_max_depth_argument(self):
schema = self.schema_class(self.col_xsd_file)
self.assertEqual(
schema.decode(self.col_xml_file, max_depth=1),
{'@xmlns:col': 'http://example.com/ns/collection',
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'@xsi:schemaLocation': 'http://example.com/ns/collection collection.xsd'})
xmlschema.limits.MAX_XML_DEPTH = 1
with self.assertRaises(XMLSchemaValidationError):
self.assertEqual(schema.decode(self.col_xml_file))
xmlschema.limits.MAX_XML_DEPTH = 9999
self.assertEqual(
schema.decode(self.col_xml_file, max_depth=2),
{'@xmlns:col': 'http://example.com/ns/collection',
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'@xsi:schemaLocation': 'http://example.com/ns/collection collection.xsd',
'object': [{'@id': 'b0836217462', '@available': True},
{'@id': 'b0836217463', '@available': True}]})
class TestValidation11(TestValidation):
schema_class = XMLSchema11

View File

@ -142,10 +142,12 @@ class TestXMLSchema10(XsdValidatorTestCase):
"Remote networks are not accessible or avoid SSL verification error on Windows.")
def test_remote_schemas_loading(self):
col_schema = self.schema_class("https://raw.githubusercontent.com/brunato/xmlschema/master/"
"xmlschema/tests/test_cases/examples/collection/collection.xsd")
"xmlschema/tests/test_cases/examples/collection/collection.xsd",
timeout=300)
self.assertTrue(isinstance(col_schema, self.schema_class))
vh_schema = self.schema_class("https://raw.githubusercontent.com/brunato/xmlschema/master/"
"xmlschema/tests/test_cases/examples/vehicles/vehicles.xsd")
"xmlschema/tests/test_cases/examples/vehicles/vehicles.xsd",
timeout=300)
self.assertTrue(isinstance(vh_schema, self.schema_class))
def test_schema_defuse(self):

View File

@ -458,14 +458,12 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
text = self.fixed if self.fixed is not None else self.default
return self.type.text_decode(text)
def iter_decode(self, elem, validation='lax', converter=None, level=0, **kwargs):
def iter_decode(self, elem, validation='lax', **kwargs):
"""
Creates an iterator for decoding an Element instance.
:param elem: the Element that has to be decoded.
:param validation: the validation mode, can be 'lax', 'strict' or 'skip.
:param converter: an :class:`XMLSchemaConverter` subclass or instance to use for the decoding.
:param level: the depth of the element in the tree structure.
:param kwargs: keyword arguments for the decoding process.
:return: yields a decoded object, eventually preceded by a sequence of \
validation or decoding errors.
@ -473,8 +471,19 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
if self.abstract:
yield self.validation_error(validation, "cannot use an abstract element for validation", elem, **kwargs)
if not isinstance(converter, XMLSchemaConverter):
converter = self.schema.get_converter(converter, level=level, **kwargs)
try:
level = kwargs['level']
except KeyError:
level = 0
try:
converter = kwargs['converter']
except KeyError:
converter = kwargs['converter'] = self.get_converter(**kwargs)
else:
if not isinstance(converter, XMLSchemaConverter):
converter = kwargs['converter'] = self.get_converter(**kwargs)
inherited = kwargs.get('inherited')
value = content = attributes = None
@ -492,7 +501,7 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
# Decode attributes
attribute_group = self.get_attributes(xsd_type)
for result in attribute_group.iter_decode(elem.attrib, validation, level=level, **kwargs):
for result in attribute_group.iter_decode(elem.attrib, validation, **kwargs):
if isinstance(result, XMLSchemaValidationError):
yield self.validation_error(validation, result, elem, **kwargs)
else:
@ -529,8 +538,7 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
for error in assertion(elem, **kwargs):
yield self.validation_error(validation, error, **kwargs)
for result in xsd_type.content_type.iter_decode(
elem, validation, converter, level + 1, **kwargs):
for result in xsd_type.content_type.iter_decode(elem, validation, **kwargs):
if isinstance(result, XMLSchemaValidationError):
yield self.validation_error(validation, result, elem, **kwargs)
else:
@ -601,29 +609,40 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
del content
if validation != 'skip':
for constraint in self.identities.values():
if isinstance(constraint, XsdKeyref) and '_no_deep' in kwargs: # TODO: Complete lazy validation
continue
for error in constraint(elem, converter):
yield self.validation_error(validation, error, elem, **kwargs)
if 'max_depth' in kwargs:
# Don't check key references with lazy or shallow validation
for constraint in filter(lambda x: not isinstance(x, XsdKeyref), self.identities.values()):
for error in constraint(elem, converter):
yield self.validation_error(validation, error, elem, **kwargs)
else:
for constraint in self.identities.values():
for error in constraint(elem, converter):
yield self.validation_error(validation, error, elem, **kwargs)
def iter_encode(self, obj, validation='lax', converter=None, level=0, **kwargs):
def iter_encode(self, obj, validation='lax', **kwargs):
"""
Creates an iterator for encoding data to an Element.
:param obj: the data that has to be encoded.
:param validation: the validation mode: can be 'lax', 'strict' or 'skip'.
:param converter: an :class:`XMLSchemaConverter` subclass or instance to use \
for the encoding.
:param level: the depth of the element data in the tree structure.
:param kwargs: keyword arguments for the encoding process.
:return: yields an Element, eventually preceded by a sequence of \
validation or encoding errors.
"""
if not isinstance(converter, XMLSchemaConverter):
converter = self.schema.get_converter(converter, level=level, **kwargs)
element_data = converter.element_encode(obj, self, level)
try:
converter = kwargs['converter']
except KeyError:
converter = kwargs['converter'] = self.get_converter(**kwargs)
else:
if not isinstance(converter, XMLSchemaConverter):
converter = kwargs['converter'] = self.get_converter(**kwargs)
try:
level = kwargs['level']
except KeyError:
level = 0
element_data = converter.element_encode(obj, self, level)
errors = []
tag = element_data.tag
text = None
@ -683,8 +702,7 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
else:
text = result
else:
for result in xsd_type.content_type.iter_encode(
element_data, validation, converter, level + 1, **kwargs):
for result in xsd_type.content_type.iter_encode(element_data, validation, **kwargs):
if isinstance(result, XMLSchemaValidationError):
errors.append(result)
elif result:

View File

@ -15,7 +15,6 @@ from __future__ import unicode_literals
from ..compat import PY3, string_base_type
from ..exceptions import XMLSchemaException, XMLSchemaWarning, XMLSchemaValueError
from ..namespaces import get_namespace
from ..qnames import qname_to_prefixed
from ..etree import etree_tostring, etree_getpath
from ..helpers import is_etree_element

View File

@ -14,6 +14,7 @@ This module contains classes for XML Schema model groups.
from __future__ import unicode_literals
import warnings
from .. import limits
from ..compat import unicode_type
from ..exceptions import XMLSchemaValueError
from ..etree import etree_element
@ -555,15 +556,12 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
msg = "Maybe a not equivalent type table between elements %r and %r." % (self, xsd_element)
warnings.warn(msg, XMLSchemaTypeTableWarning, stacklevel=3)
def iter_decode(self, elem, validation='lax', converter=None, level=0, **kwargs):
def iter_decode(self, elem, validation='lax', **kwargs):
"""
Creates an iterator for decoding an Element content.
:param elem: the Element that has to be decoded.
:param validation: the validation mode, can be 'lax', 'strict' or 'skip.
:param converter: an :class:`XMLSchemaConverter` subclass or instance \
to use for the decoding.
:param level: the depth of the element in the tree structure.
:param kwargs: keyword arguments for the decoding process.
:return: yields a list of 3-tuples (key, decoded data, decoder), \
eventually preceded by a sequence of validation or decoding errors.
@ -590,16 +588,21 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
result_list.append((cdata_index, text, None))
cdata_index += 1
model = ModelVisitor(self)
errors = []
level = kwargs['level'] = kwargs.pop('level', 0) + 1
if level > limits.MAX_XML_DEPTH:
reason = "XML data depth exceeded (MAX_XML_DEPTH=%r)" % limits.MAX_XML_DEPTH
self.validation_error('strict', reason, elem, **kwargs)
try:
default_namespace = converter.get('')
except (AttributeError, TypeError):
converter = self.schema.get_converter(converter, level=level, **kwargs)
default_namespace = converter.get('')
converter = kwargs['converter']
except KeyError:
converter = kwargs['converter'] = self.get_converter(**kwargs)
default_namespace = converter.get('')
model = ModelVisitor(self)
errors = []
model_broken = False
for index, child in enumerate(elem):
if callable(child.tag):
continue # child is a <class 'lxml.etree._Comment'>
@ -646,12 +649,13 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
xsd_element = None
model_broken = True
if xsd_element is None or kwargs.get('no_depth'):
# TODO: use a default decoder str-->str??
if 'max_depth' in kwargs and kwargs['max_depth'] <= level:
continue
elif xsd_element is None:
# TODO: apply a default decoder str-->str??
continue
for result in xsd_element.iter_decode(
child, validation, converter=converter, level=level, **kwargs):
for result in xsd_element.iter_decode(child, validation, **kwargs):
if isinstance(result, XMLSchemaValidationError):
yield result
else:
@ -678,16 +682,12 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
yield result_list
def iter_encode(self, element_data, validation='lax', converter=None, level=0, indent=4, **kwargs):
def iter_encode(self, element_data, validation='lax', **kwargs):
"""
Creates an iterator for encoding data to a list containing Element data.
:param element_data: an ElementData instance with unencoded data.
:param validation: the validation mode: can be 'lax', 'strict' or 'skip'.
:param converter: an :class:`XMLSchemaConverter` subclass or instance to use \
for the encoding.
:param level: the depth of the element data in the tree structure.
:param indent: number of spaces for XML indentation (default is 4).
:param kwargs: keyword arguments for the encoding process.
:return: yields a couple with the text of the Element and a list of 3-tuples \
(key, decoded data, decoder), eventually preceded by a sequence of validation \
@ -697,19 +697,26 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
yield element_data.content
return
level = kwargs['level'] = kwargs.pop('level', 0) + 1
errors = []
text = None
children = []
try:
indent = kwargs['indent']
except KeyError:
indent = 4
padding = '\n' + ' ' * indent * level
try:
default_namespace = converter.get('')
except (AttributeError, TypeError):
converter = self.schema.get_converter(converter, level=level, **kwargs)
default_namespace = converter.get('')
converter = kwargs['converter']
except KeyError:
converter = kwargs['converter'] = self.get_converter(**kwargs)
default_namespace = converter.get('')
model = ModelVisitor(self)
cdata_index = 0
if isinstance(element_data.content, dict) or kwargs.get('unordered'):
content = model.iter_unordered_content(element_data.content)
elif not isinstance(element_data.content, list):
@ -766,8 +773,7 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
yield self.validation_error(validation, reason, value, **kwargs)
continue
for result in xsd_element.iter_encode(
value, validation, converter=converter, level=level, indent=indent, **kwargs):
for result in xsd_element.iter_encode(value, validation, **kwargs):
if isinstance(result, XMLSchemaValidationError):
yield result
else:

View File

@ -14,17 +14,13 @@ This module contains classes and functions for processing XSD content models.
from __future__ import unicode_literals
from collections import defaultdict, deque, Counter
from .. import limits
from ..compat import PY3, MutableSequence
from ..exceptions import XMLSchemaValueError
from .exceptions import XMLSchemaModelError, XMLSchemaModelDepthError
from .xsdbase import ParticleMixin
from .wildcards import XsdAnyElement, Xsd11AnyElement
MAX_MODEL_DEPTH = 15
"""Limit depth for safe visiting of models"""
XSD_GROUP_MODELS = {'sequence', 'choice', 'all'}
class ModelGroup(MutableSequence, ParticleMixin):
"""
@ -34,7 +30,6 @@ class ModelGroup(MutableSequence, ParticleMixin):
parent = None
def __init__(self, model):
assert model in XSD_GROUP_MODELS, "Not a valid value for 'model'"
self._group = []
self.model = model
@ -61,7 +56,7 @@ class ModelGroup(MutableSequence, ParticleMixin):
def __setattr__(self, name, value):
if name == 'model' and value is not None:
if value not in XSD_GROUP_MODELS:
if value not in {'sequence', 'choice', 'all'}:
raise XMLSchemaValueError("invalid model group %r." % value)
if self.model is not None and value != self.model and self.model != 'all':
raise XMLSchemaValueError("cannot change group model from %r to %r" % (self.model, value))
@ -165,11 +160,11 @@ class ModelGroup(MutableSequence, ParticleMixin):
"""
A generator function iterating elements and groups of a model group. Skips pointless groups,
iterating deeper through them. Raises `XMLSchemaModelDepthError` if the argument *depth* is
over `MAX_MODEL_DEPTH` value.
over `limits.MAX_MODEL_DEPTH` value.
:param depth: guard for protect model nesting bombs, incremented at each deepest recursion.
"""
if depth > MAX_MODEL_DEPTH:
if depth > limits.MAX_MODEL_DEPTH:
raise XMLSchemaModelDepthError(self)
for item in self:
if not isinstance(item, ModelGroup):
@ -183,11 +178,11 @@ class ModelGroup(MutableSequence, ParticleMixin):
def iter_elements(self, depth=0):
"""
A generator function iterating model's elements. Raises `XMLSchemaModelDepthError` if the
argument *depth* is over `MAX_MODEL_DEPTH` value.
argument *depth* is over `limits.MAX_MODEL_DEPTH` value.
:param depth: guard for protect model nesting bombs, incremented at each deepest recursion.
"""
if depth > MAX_MODEL_DEPTH:
if depth > limits.MAX_MODEL_DEPTH:
raise XMLSchemaModelDepthError(self)
for item in self:
if isinstance(item, ModelGroup):
@ -203,12 +198,12 @@ class ModelGroup(MutableSequence, ParticleMixin):
:raises: an `XMLSchemaModelError` at first violated constraint.
"""
def safe_iter_path(group, depth):
if depth > MAX_MODEL_DEPTH:
if not depth:
raise XMLSchemaModelDepthError(group)
for item in group:
if isinstance(item, ModelGroup):
current_path.append(item)
for _item in safe_iter_path(item, depth + 1):
for _item in safe_iter_path(item, depth - 1):
yield _item
current_path.pop()
else:
@ -221,7 +216,7 @@ class ModelGroup(MutableSequence, ParticleMixin):
except AttributeError:
any_element = None
for e in safe_iter_path(self, 0):
for e in safe_iter_path(self, limits.MAX_MODEL_DEPTH):
for pe, previous_path in paths.values():
# EDC check
if not e.is_consistent(pe) or any_element and not any_element.is_consistent(pe):

View File

@ -822,27 +822,6 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
except KeyError:
return []
def get_converter(self, converter=None, namespaces=None, **kwargs):
"""
Returns a new converter instance.
:param converter: can be a converter class or instance. If it's an instance \
the new instance is copied from it and configured with the provided arguments.
:param namespaces: is an optional mapping from namespace prefix to URI.
:param kwargs: optional arguments for initialize the converter instance.
:return: a converter instance.
"""
if converter is None:
converter = getattr(self, 'converter', XMLSchemaConverter)
if isinstance(converter, XMLSchemaConverter):
return converter.copy(namespaces=namespaces, **kwargs)
elif issubclass(converter, XMLSchemaConverter):
return converter(namespaces, **kwargs)
else:
msg = "'converter' argument must be a %r subclass or instance: %r"
raise XMLSchemaTypeError(msg % (XMLSchemaConverter, converter))
def get_element(self, tag, path=None, namespaces=None):
if not path:
return self.find(tag, namespaces)
@ -1223,16 +1202,14 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
inherited = {}
if source.is_lazy() and path is None:
# TODO: Document validation in lazy mode.
# Validation is done pushing a _no_deep argument for root node and with
# a path='*' for validating children. This is a feature under test.
xsd_element = self.get_element(source.root.tag, schema_path)
if xsd_element is None:
yield self.validation_error('lax', "%r is not an element of the schema" % source.root, source.root)
msg = "%r is not an element of the schema"
yield self.validation_error('lax', msg % source.root, source.root)
for result in xsd_element.iter_decode(source.root, source=source, namespaces=namespaces,
use_defaults=use_defaults, id_map=id_map, no_depth=True,
inherited=inherited, drop_results=True):
use_defaults=use_defaults, id_map=id_map,
inherited=inherited, max_depth=1):
if isinstance(result, XMLSchemaValidationError):
yield result
else:
@ -1249,7 +1226,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
for result in xsd_element.iter_decode(elem, source=source, namespaces=namespaces,
use_defaults=use_defaults, id_map=id_map,
inherited=inherited, drop_results=True):
inherited=inherited):
if isinstance(result, XMLSchemaValidationError):
yield result
else:
@ -1264,7 +1241,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
def iter_decode(self, source, path=None, schema_path=None, validation='lax', process_namespaces=True,
namespaces=None, use_defaults=True, decimal_type=None, datetime_types=False,
converter=None, filler=None, fill_missing=False, **kwargs):
converter=None, filler=None, fill_missing=False, max_depth=None, **kwargs):
"""
Creates an iterator for decoding an XML source to a data structure.
@ -1292,6 +1269,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
an attribute declaration. If not provided undecodable data is replaced by `None`.
:param fill_missing: if set to `True` the decoder fills also missing attributes. \
The filling value is `None` or a typed value if the *filler* callback is provided.
:param max_depth: maximum level of decoding. For default has no limit.
:param kwargs: keyword arguments with other options for converter and decoder.
:return: yields a decoded data object, eventually preceded by a sequence of validation \
or decoding errors.
@ -1323,6 +1301,8 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
kwargs['decimal_type'] = decimal_type
if filler is not None:
kwargs['filler'] = filler
if max_depth is not None:
kwargs['max_depth'] = max_depth
for elem in source.iterfind(path, namespaces):
xsd_element = self.get_element(elem.tag, schema_path, namespaces)

View File

@ -21,6 +21,7 @@ from ..qnames import XSD_ANNOTATION, XSD_APPINFO, XSD_DOCUMENTATION, XML_LANG, \
get_qname, local_name, qname_to_prefixed
from ..etree import etree_tostring
from ..helpers import is_etree_element
from ..converters import XMLSchemaConverter
from .exceptions import XMLSchemaParseError, XMLSchemaValidationError, \
XMLSchemaDecodeError, XMLSchemaEncodeError
@ -195,6 +196,27 @@ class XsdValidator(object):
self.parse_error(msg % (value, ' | '.join(admitted_values)), elem)
return ''
def get_converter(self, converter=None, namespaces=None, **kwargs):
"""
Returns a new converter instance.
:param converter: can be a converter class or instance. If it's an instance \
the new instance is copied from it and configured with the provided arguments.
:param namespaces: is an optional mapping from namespace prefix to URI.
:param kwargs: optional arguments for initialize the converter instance.
:return: a converter instance.
"""
if converter is None:
converter = getattr(self, 'converter', XMLSchemaConverter)
if isinstance(converter, XMLSchemaConverter):
return converter.copy(namespaces=namespaces, **kwargs)
elif issubclass(converter, XMLSchemaConverter):
return converter(namespaces, **kwargs)
else:
msg = "'converter' argument must be a %r subclass or instance: %r"
raise XMLSchemaTypeError(msg % (XMLSchemaConverter, converter))
class XsdComponent(XsdValidator):
"""