# -*- 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 # """ 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."""