# Copyright (c) 2015, The MITRE Corporation. All rights reserved.
# See LICENSE.txt for complete terms.
# external
import lxml.isoschematron
# internal
from sdv import utils, xmlconst
# relative
from . import base
[docs]class SchematronError(base.ValidationError):
"""Represents an error found in a SVRL report.
Args:
doc: The instance document which was validated and produced this error.
error: The ``svrl:failed-assert`` or ``svrl:successful-report``
``etree._Element`` instance.
Attributes:
message: The validation error message.
"""
def __init__(self, doc, error):
super(SchematronError, self).__init__()
self._doc = doc
self._error = error
self._xpath_location = error.attrib.get('location')
self._test = error.attrib.get('test')
self._line = None
self.message = self._parse_message(error)
def __unicode__(self):
return unicode(self.message)
def __str__(self):
return unicode(self).encode("utf-8")
def _get_line(self):
"""Returns the line number in the input document associated with this
error.
"""
root = utils.get_etree_root(self._doc)
xpath = self._xpath_location
nsmap = self._error.nsmap
node = root.xpath(xpath, namespaces=nsmap)[0]
return node.sourceline
@property
[docs] def line(self):
"""Returns the line number in the input document associated with this
error.
This property is lazily evaluated, meaning the line number isn't known
until the first time this property is accessed. Each subsequent call
will return the cached line number.
"""
if not self._line:
self._line = self._get_line()
return self._line
def _parse_message(self, error):
message = error.find("{%s}text" % xmlconst.NS_SVRL)
if message is None:
return ""
return message.text
[docs] def as_dict(self):
"""Returns a dictionary representation.
Keys:
* ``'message'``: The error message
* ``'line'``: The line number associated with the error
"""
return dict(message=self.message, line=self.line)
[docs]class SchematronValidationResults(base.ValidationResults):
"""Used to hold results of a Schematron validation process.
Args:
is_valid: The boolean validation result.
doc: The document which produced these validation results.
svrl_report: The etree._ElementTree SVRL report produced during the
validation run.
Attributes:
errors: A list of :class:`SchematronError` instances representing
errors found in the `svrl_report`.
is_valid: Returns ``True`` if the validation was successful and
``False`` otherwise.
"""
def __init__(self, is_valid, doc=None, svrl_report=None):
super(SchematronValidationResults, self).__init__(is_valid)
self._svrl_report = svrl_report
self._doc = doc
self.errors = self._parse_errors(svrl_report)
def _parse_errors(self, svrl_report):
if not svrl_report:
return []
xpath = "//svrl:failed-assert | //svrl:successful-report"
nsmap = {'svrl': xmlconst.NS_SVRL}
errors = svrl_report.xpath(xpath, namespaces=nsmap)
return [SchematronError(self._doc, x) for x in errors]
[docs] def as_dict(self):
"""A dictionary representation of the
:class:`.SchematronValidationResults` instance.
Keys:
* ``'result'``: The validation results. Values can be
``True`` or ``False``.
* ``'errors'``: A list of validation error dictionaries. The keys
are ``'message'`` and ``'line'``.
Returns:
A dictionary representation of an instance of this class.
"""
d = super(SchematronValidationResults, self).as_dict()
if self.errors:
d['errors'] = [x.as_dict() for x in self.errors]
return d
[docs]class SchematronValidator(object):
"""Performs schematron validation against an XML instance document.
Args:
schematron: A Schematron document. This can be a filename, file-like
object, ``etree._Element``, or ``etree._ElementTree`` instance.
"""
def __init__(self, schematron):
self._schematron = self._build_schematron(schematron)
def _build_schematron(self, sch):
"""Attempts to build an ``lxml.isoschematron.Schematron`` instance
from `sch`.
Args:
sch: A Schematron document filename, file-like object,
etree._Element, or etree._ElementTree.
Returns:
A ``lxml.isoschematron.Schematron`` instance for `sch`.
"""
if sch is None:
raise ValueError("Input schematron document cannot be None")
root = utils.get_etree_root(sch)
schematron = lxml.isoschematron.Schematron(
root,
store_report=True,
store_xslt=True,
store_schematron=True
)
return schematron
@property
def xslt(self):
"""Returns an etree._ElementTree representation of the XSLT
transform of the Schematron document.
"""
return self._schematron.validator_xslt
@property
def schematron(self):
"""Returns an etree._ElementTree representation of the Schematron
document.
"""
return self._schematron.schematron
[docs] def validate(self, doc):
"""Validates an XML instance document `doc` using Schematron rules.
Args:
doc: An XML instance document. This can be a filename, file-like
object, ``etree._Element`` or ``etree._ElementTree`` instance.
Returns:
An instance of
:class:`.SchematronValidationResults`.
Raises:
.ValidationError: If there are any issues parsing `doc`.
"""
root = utils.get_etree_root(doc)
is_valid = self.schematron.validate(root)
svrl_report = self.schematron.validation_report
return SchematronValidationResults(
is_valid=is_valid,
doc=root,
svrl_report=svrl_report
)
__all__ = [
'SchematronValidator',
'SchematronValidationResults',
'SchematronError'
]