debian-xmlschema/xmlschema/validators/exceptions.py

367 lines
14 KiB
Python

# -*- 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>
#
"""
This module contains exception and warning classes for the 'xmlschema.validators' subpackage.
"""
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
from ..resources import XMLResource
class XMLSchemaValidatorError(XMLSchemaException):
"""
Base class for XSD validator errors.
:param validator: the XSD validator.
:type validator: XsdValidator or function
:param message: the error message.
:type message: str or unicode
:param elem: the element that contains the error.
:type elem: Element
:param source: the XML resource that contains the error.
:type source: XMLResource
:param namespaces: is an optional mapping from namespace prefix to URI.
:type namespaces: dict
:ivar path: the XPath of the element, calculated when the element is set or the XML resource is set.
"""
def __init__(self, validator, message, elem=None, source=None, namespaces=None):
self.path = None
self.validator = validator
self.message = message[:-1] if message[-1] in ('.', ':') else message
self.namespaces = namespaces
self.source = source
self.elem = elem
def __str__(self):
return unicode(self).encode("utf-8")
def __unicode__(self):
if self.elem is None:
return '%s.' % self.message
else:
msg = ['%s:\n' % self.message]
if self.elem is not None:
elem_as_string = etree_tostring(self.elem, self.namespaces, ' ', 20)
if hasattr(self.elem, 'sourceline'):
msg.append("Schema (line %r):\n\n%s\n" % (self.elem.sourceline, elem_as_string))
else:
msg.append("Schema:\n\n%s\n" % elem_as_string)
if self.path is not None:
msg.append("Path: %s\n" % self.path)
if self.schema_url is not None:
msg.append("Schema URL: %s\n" % self.schema_url)
if self.origin_url not in (None, self.schema_url):
msg.append("Origin URL: %s\n" % self.origin_url)
return '\n'.join(msg)
if PY3:
__str__ = __unicode__
@property
def msg(self):
return self.__unicode__()
def __setattr__(self, name, value):
if name == 'elem' and value is not None:
if not is_etree_element(value):
raise XMLSchemaValueError("'elem' attribute requires an Element, not %r." % type(value))
if self.source is not None:
self.path = etree_getpath(value, self.root, self.namespaces, relative=False, add_position=True)
if self.source.is_lazy():
value = None # Don't save the element of a lazy resource
if name == 'source' and value is not None and getattr(self, 'elem', None) is not None:
self.path = etree_getpath(self.elem, value.root, self.namespaces, relative=False, add_position=True)
if value.is_lazy():
self.elem = None
super(XMLSchemaValidatorError, self).__setattr__(name, value)
@property
def sourceline(self):
return getattr(self.elem, 'sourceline', None)
@property
def root(self):
try:
return self.source.root
except AttributeError:
return
@property
def schema_url(self):
try:
return self.validator.schema.source.url
except AttributeError:
return
@property
def origin_url(self):
try:
return self.validator.maps.validator.source.url
except AttributeError:
return
class XMLSchemaNotBuiltError(XMLSchemaValidatorError, RuntimeError):
"""
Raised when there is an improper usage attempt of a not built XSD validator.
:param validator: the XSD validator.
:type validator: XsdValidator
:param message: the error message.
:type message: str or unicode
"""
def __init__(self, validator, message):
super(XMLSchemaNotBuiltError, self).__init__(
validator=validator,
message=message,
elem=getattr(validator, 'elem', None),
source=getattr(validator, 'source', None),
namespaces=getattr(validator, 'namespaces', None)
)
class XMLSchemaParseError(XMLSchemaValidatorError, SyntaxError):
"""
Raised when an error is found during the building of an XSD validator.
:param validator: the XSD validator.
:type validator: XsdValidator or function
:param message: the error message.
:type message: str or unicode
:param elem: the element that contains the error.
:type elem: Element
"""
def __init__(self, validator, message, elem=None):
super(XMLSchemaParseError, self).__init__(
validator=validator,
message=message,
elem=elem if elem is not None else getattr(validator, 'elem', None),
source=getattr(validator, 'source', None),
namespaces=getattr(validator, 'namespaces', None),
)
class XMLSchemaModelError(XMLSchemaValidatorError, ValueError):
"""
Raised when a model error is found during the checking of a model group.
:param group: the XSD model group.
:type group: XsdGroup
:param message: the error message.
:type message: str or unicode
"""
def __init__(self, group, message):
super(XMLSchemaModelError, self).__init__(
validator=group,
message=message,
elem=group.elem,
source=getattr(group, 'source', None),
namespaces=group.namespaces
)
class XMLSchemaModelDepthError(XMLSchemaModelError):
"""Raised when recursion depth is exceeded while iterating a model group."""
def __init__(self, group):
msg = "maximum model recursion depth exceeded while iterating group %r" % group
super(XMLSchemaModelDepthError, self).__init__(group, message=msg)
class XMLSchemaValidationError(XMLSchemaValidatorError, ValueError):
"""
Raised when the XML data is not validated with the XSD component or schema.
It's used by decoding and encoding methods. Encoding validation errors do
not include XML data element and source, so the error is limited to a message
containing object representation and a reason.
:param validator: the XSD validator.
:type validator: XsdValidator or function
:param obj: the not validated XML data.
:type obj: Element or tuple or str or list or int or float or bool
:param reason: the detailed reason of failed validation.
:type reason: str or unicode
:param source: the XML resource that contains the error.
:type source: XMLResource
:param namespaces: is an optional mapping from namespace prefix to URI.
:type namespaces: dict
"""
def __init__(self, validator, obj, reason=None, source=None, namespaces=None):
if not isinstance(obj, string_base_type):
_obj = obj
else:
_obj = obj.encode('ascii', 'xmlcharrefreplace').decode('utf-8')
super(XMLSchemaValidationError, self).__init__(
validator=validator,
message="failed validating {!r} with {!r}".format(_obj, validator),
elem=obj if is_etree_element(obj) else None,
source=source,
namespaces=namespaces,
)
self.obj = obj
self.reason = reason
def __str__(self):
# noinspection PyCompatibility,PyUnresolvedReferences
return unicode(self).encode("utf-8")
def __unicode__(self):
msg = ['%s:\n' % self.message]
if self.reason is not None:
msg.append('Reason: %s\n' % self.reason)
if hasattr(self.validator, 'tostring'):
msg.append("Schema:\n\n%s\n" % self.validator.tostring(' ', 20))
if is_etree_element(self.elem):
try:
elem_as_string = etree_tostring(self.elem, self.namespaces, ' ', 20)
except (ValueError, TypeError):
elem_as_string = repr(self.elem)
if hasattr(self.elem, 'sourceline'):
msg.append("Instance (line %r):\n\n%s\n" % (self.elem.sourceline, elem_as_string))
else:
msg.append("Instance:\n\n%s\n" % elem_as_string)
if self.path is not None:
msg.append("Path: %s\n" % self.path)
return '\n'.join(msg)
if PY3:
__str__ = __unicode__
class XMLSchemaDecodeError(XMLSchemaValidationError):
"""
Raised when an XML data string is not decodable to a Python object.
:param validator: the XSD validator.
:type validator: XsdValidator or function
:param obj: the not validated XML data.
:type obj: Element or tuple or str or list or int or float or bool
:param decoder: the XML data decoder.
:type decoder: type or function
:param reason: the detailed reason of failed validation.
:type reason: str or unicode
:param source: the XML resource that contains the error.
:type source: XMLResource
:param namespaces: is an optional mapping from namespace prefix to URI.
:type namespaces: dict
"""
message = "failed decoding {!r} with {!r}.\n"
def __init__(self, validator, obj, decoder, reason=None, source=None, namespaces=None):
super(XMLSchemaDecodeError, self).__init__(validator, obj, reason, source, namespaces)
self.decoder = decoder
class XMLSchemaEncodeError(XMLSchemaValidationError):
"""
Raised when an object is not encodable to an XML data string.
:param validator: the XSD validator.
:type validator: XsdValidator or function
:param obj: the not validated XML data.
:type obj: Element or tuple or str or list or int or float or bool
:param encoder: the XML encoder.
:type encoder: type or function
:param reason: the detailed reason of failed validation.
:type reason: str or unicode
:param source: the XML resource that contains the error.
:type source: XMLResource
:param namespaces: is an optional mapping from namespace prefix to URI.
:type namespaces: dict
"""
message = "failed encoding {!r} with {!r}.\n"
def __init__(self, validator, obj, encoder, reason=None, source=None, namespaces=None):
super(XMLSchemaEncodeError, self).__init__(validator, obj, reason, source, namespaces)
self.encoder = encoder
class XMLSchemaChildrenValidationError(XMLSchemaValidationError):
"""
Raised when a child element is not validated.
:param validator: the XSD validator.
:type validator: XsdValidator or function
:param elem: the not validated XML element.
:type elem: Element or ElementData
:param index: the child index.
:type index: int
:param particle: the validator particle that generated the error. Maybe the validator itself.
:type particle: ParticleMixin
:param occurs: the particle occurrences.
:type occurs: int
:param expected: the expected element tags/object names.
:type expected: str or list or tuple
:param source: the XML resource that contains the error.
:type source: XMLResource
:param namespaces: is an optional mapping from namespace prefix to URI.
:type namespaces: dict
"""
def __init__(self, validator, elem, index, particle, occurs=0, expected=None, source=None, namespaces=None):
self.index = index
self.particle = particle
self.occurs = occurs
self.expected = expected
tag = qname_to_prefixed(elem.tag, validator.namespaces, use_empty=False)
if index >= len(elem):
reason = "The content of element %r is not complete." % tag
else:
child_tag = qname_to_prefixed(elem[index].tag, validator.namespaces, use_empty=False)
reason = "Unexpected child with tag %r at position %d." % (child_tag, index + 1)
if occurs and particle.is_missing(occurs):
reason += " The particle %r occurs %d times but the minimum is %d." % (
particle, occurs, particle.min_occurs
)
elif particle.is_over(occurs):
reason += " The particle %r occurs %d times but the maximum is %d." % (
particle, occurs, particle.max_occurs
)
if expected is None:
pass
else:
expected_tags = []
for xsd_element in expected:
if xsd_element.name is not None:
expected_tags.append(xsd_element.prefixed_name)
elif xsd_element.process_contents == 'strict':
expected_tags.append('from %r namespace/s' % xsd_element.namespace)
if not expected_tags:
pass # reason += " No child element is expected at this point." <-- this can be misleading
elif len(expected_tags) == 1:
reason += " Tag %s expected." % expected_tags[0]
else:
reason += " Tag (%s) expected." % ' | '.join(expected_tags)
super(XMLSchemaChildrenValidationError, self).__init__(validator, elem, reason, source, namespaces)
class XMLSchemaIncludeWarning(XMLSchemaWarning):
"""A schema include fails."""
class XMLSchemaImportWarning(XMLSchemaWarning):
"""A schema namespace import fails."""
class XMLSchemaTypeTableWarning(XMLSchemaWarning):
"""Not equivalent type table found in model."""