# -*- coding: utf-8 -*- """ Test cases related to ISO-Schematron parsing and validation """ from __future__ import absolute_import import unittest from lxml import isoschematron from .common_imports import etree, HelperTestCase, fileInTestDir, doctest, make_doctest class ETreeISOSchematronTestCase(HelperTestCase): def test_schematron(self): tree_valid = self.parse('') tree_invalid = self.parse('') schema = self.parse('''\ Open Model BBB element is not present CCC element is not present Closed model" BBB element is not present CCC element is not present There is an extra element ''') schema = isoschematron.Schematron(schema) self.assertTrue(schema.validate(tree_valid)) self.assertTrue(not schema.validate(tree_invalid)) def test_schematron_elementtree_error(self): self.assertRaises(ValueError, isoschematron.Schematron, etree.ElementTree()) # an empty pattern is valid in iso schematron def test_schematron_empty_pattern(self): schema = self.parse('''\ Open model ''') schema = isoschematron.Schematron(schema) self.assertTrue(schema) def test_schematron_invalid_schema_empty(self): schema = self.parse('''\ ''') self.assertRaises(etree.SchematronParseError, isoschematron.Schematron, schema) def test_schematron_invalid_schema_namespace(self): schema = self.parse('''\ ''') self.assertRaises(etree.SchematronParseError, isoschematron.Schematron, schema) def test_schematron_from_tree(self): schema = self.parse('''\ mandatory number_of_entries tests [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') schematron = isoschematron.Schematron(schema) self.assertTrue(isinstance(schematron, isoschematron.Schematron)) def test_schematron_from_element(self): schema = self.parse('''\ mandatory number_of_entries tests [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') schematron = isoschematron.Schematron(schema.getroot()) self.assertTrue(isinstance(schematron, isoschematron.Schematron)) def test_schematron_from_file(self): schematron = isoschematron.Schematron(file=fileInTestDir('test.sch')) self.assertTrue(isinstance(schematron, isoschematron.Schematron)) def test_schematron_call(self): schema = self.parse('''\ mandatory number_of_entries tests [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') tree_valid = self.parse('''\ 0 ''') tree_invalid = self.parse('''\ 3 Entry 1 Entry 2 ''') schematron = isoschematron.Schematron(schema) self.assertTrue(schematron(tree_valid), schematron.error_log) valid = schematron(tree_invalid) self.assertTrue(not valid) def test_schematron_validate(self): schema = self.parse('''\ mandatory number_of_entries tests [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') tree_valid = self.parse('''\ 0 ''') tree_invalid = self.parse('''\ 3 Entry 1 Entry 2 ''') schematron = isoschematron.Schematron(schema) self.assertTrue(schematron.validate(tree_valid), schematron.error_log) valid = schematron.validate(tree_invalid) self.assertTrue(not valid) def test_schematron_assertValid(self): schema = self.parse('''\ mandatory number_of_entries tests [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') tree_valid = self.parse('''\ 0 ''') tree_invalid = self.parse('''\ 3 Entry 1 Entry 2 ''') schematron = isoschematron.Schematron(schema) self.assertTrue(schematron(tree_valid), schematron.error_log) self.assertRaises(etree.DocumentInvalid, schematron.assertValid, tree_invalid) def test_schematron_error_log(self): schema = self.parse('''\ mandatory number_of_entries tests [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') tree_valid = self.parse('''\ 0 ''') tree_invalid = self.parse('''\ 3 Entry 1 Entry 2 ''') schematron = isoschematron.Schematron(schema) self.assertTrue(schematron(tree_valid), schematron.error_log) valid = schematron(tree_invalid) self.assertTrue(not valid) self.assertEqual(len(schematron.error_log), 1, 'expected single error: %s (%s errors)' % (schematron.error_log, len(schematron.error_log))) def test_schematron_result_report(self): schema = self.parse('''\ mandatory number_of_entries tests [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') tree_valid = self.parse('''\ 0 ''') tree_invalid = self.parse('''\ 3 Entry 1 Entry 2 ''') schematron = isoschematron.Schematron(schema, store_report=True) self.assertTrue(schematron(tree_valid), schematron.error_log) valid = schematron(tree_invalid) self.assertTrue(not valid) self.assertTrue( isinstance(schematron.validation_report, etree._ElementTree), 'expected a validation report result tree, got: %s' % schematron.validation_report) schematron = isoschematron.Schematron(schema, store_report=False) self.assertTrue(schematron(tree_valid), schematron.error_log) valid = schematron(tree_invalid) self.assertTrue(not valid) self.assertTrue(schematron.validation_report is None, 'validation reporting switched off, still: %s' % schematron.validation_report) def test_schematron_store_schematron(self): schema = self.parse('''\ mandatory number_of_entries tests [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') schematron = isoschematron.Schematron(schema) self.assertTrue(schematron.validator_xslt is None) schematron = isoschematron.Schematron(schema, store_schematron=True) self.assertTrue(isinstance(schematron.schematron, etree._ElementTree), 'expected schematron schema to be stored') def test_schematron_store_xslt(self): schema = self.parse('''\ mandatory number_of_entries tests [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') schematron = isoschematron.Schematron(schema) self.assertTrue(schematron.validator_xslt is None) schematron = isoschematron.Schematron(schema, store_xslt=True) self.assertTrue(isinstance(schematron.validator_xslt, etree._ElementTree), 'expected validator xslt to be stored') def test_schematron_abstract(self): schema = self.parse('''\ iso schematron validation [ERROR] element () dateTime value () is not qualified as UTC (tz: ) [ERROR] element () dateTime value () is not qualified as UTC (tz: ) ''') valid_trees = [ self.parse('''\ 2009-12-10T15:21:00Z '''), self.parse('''\ 2009-12-10T15:21:00Z 2009-12-10T15:21:00Z '''), self.parse('''\ 2009-12-10T15:21:00+00:00 2009-12-10T15:21:00-00:00 '''), ] schematron = isoschematron.Schematron(schema) for tree_valid in valid_trees: self.assertTrue(schematron(tree_valid), schematron.error_log) tree_invalid = self.parse('''\ 2009-12-10T16:21:00+01:00 2009-12-10T16:21:00+01:00 ''') expected = 2 valid = schematron(tree_invalid) self.assertTrue(not valid) self.assertEqual( len(schematron.error_log), expected, 'expected %s errors: %s (%s errors)' % (expected, schematron.error_log, len(schematron.error_log))) tree_invalid = self.parse('''\ 2009-12-10T16:21:00Z ''') expected = 1 valid = schematron(tree_invalid) self.assertTrue(not valid) self.assertEqual( len(schematron.error_log), expected, 'expected %s errors: %s (%s errors)' % (expected, schematron.error_log, len(schematron.error_log))) def test_schematron_phases(self): schema = self.parse('''\ iso schematron validation [ERROR] element () dateTime value () is not qualified as UTC (tz: ) [ERROR] element () dateTime value () is not qualified as UTC (tz: ) mandatory number_of_entries test [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') tree_valid = self.parse('''\ 2009-12-10T15:21:00Z 0 ''') tree_invalid = self.parse('''\ 2009-12-10T16:21:00+01:00 2009-12-10T16:21:00+01:00 3 Entry 1 Entry 2 ''') # check everything (default phase #ALL) schematron = isoschematron.Schematron(schema) self.assertTrue(schematron(tree_valid), schematron.error_log) expected = 3 valid = schematron(tree_invalid) self.assertTrue(not valid) self.assertEqual( len(schematron.error_log), expected, 'expected %s errors: %s (%s errors)' % (expected, schematron.error_log, len(schematron.error_log))) # check phase mandatory schematron = isoschematron.Schematron( schema, compile_params={'phase': 'mandatory'}) self.assertTrue(schematron(tree_valid), schematron.error_log) expected = 1 valid = schematron(tree_invalid) self.assertTrue(not valid) self.assertEqual( len(schematron.error_log), expected, 'expected %s errors: %s (%s errors)' % (expected, schematron.error_log, len(schematron.error_log))) # check phase datetime_checks schematron = isoschematron.Schematron( schema, compile_params={'phase': 'datetime_checks'}) self.assertTrue(schematron(tree_valid), schematron.error_log) expected = 2 valid = schematron(tree_invalid) self.assertTrue(not valid) self.assertEqual( len(schematron.error_log), expected, 'expected %s errors: %s (%s errors)' % (expected, schematron.error_log, len(schematron.error_log))) # check phase full schematron = isoschematron.Schematron( schema, compile_params={'phase': 'full'}) self.assertTrue(schematron(tree_valid), schematron.error_log) expected = 3 valid = schematron(tree_invalid) self.assertTrue(not valid) self.assertEqual( len(schematron.error_log), expected, 'expected %s errors: %s (%s errors)' % (expected, schematron.error_log, len(schematron.error_log))) def test_schematron_phases_kwarg(self): schema = self.parse('''\ iso schematron validation [ERROR] element () dateTime value () is not qualified as UTC (tz: ) [ERROR] element () dateTime value () is not qualified as UTC (tz: ) mandatory number_of_entries test [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') tree_valid = self.parse('''\ 2009-12-10T15:21:00Z 0 ''') tree_invalid = self.parse('''\ 2009-12-10T16:21:00+01:00 2009-12-10T16:21:00+01:00 3 Entry 1 Entry 2 ''') # check everything (default phase #ALL) schematron = isoschematron.Schematron(schema) self.assertTrue(schematron(tree_valid), schematron.error_log) expected = 3 valid = schematron(tree_invalid) self.assertTrue(not valid) self.assertEqual( len(schematron.error_log), expected, 'expected %s errors: %s (%s errors)' % (expected, schematron.error_log, len(schematron.error_log))) # check phase mandatory schematron = isoschematron.Schematron(schema, phase='mandatory') self.assertTrue(schematron(tree_valid), schematron.error_log) expected = 1 valid = schematron(tree_invalid) self.assertTrue(not valid) self.assertEqual( len(schematron.error_log), expected, 'expected %s errors: %s (%s errors)' % (expected, schematron.error_log, len(schematron.error_log))) # check phase datetime_checks schematron = isoschematron.Schematron(schema, phase='datetime_checks') self.assertTrue(schematron(tree_valid), schematron.error_log) expected = 2 valid = schematron(tree_invalid) self.assertTrue(not valid) self.assertEqual( len(schematron.error_log), expected, 'expected %s errors: %s (%s errors)' % (expected, schematron.error_log, len(schematron.error_log))) # check phase full schematron = isoschematron.Schematron(schema, phase='full') self.assertTrue(schematron(tree_valid), schematron.error_log) expected = 3 valid = schematron(tree_invalid) self.assertTrue(not valid) self.assertEqual( len(schematron.error_log), expected, 'expected %s errors: %s (%s errors)' % (expected, schematron.error_log, len(schematron.error_log))) def test_schematron_xmlschema_embedded(self): schema = self.parse('''\ mandatory number_of_entries tests [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') tree_valid = self.parse('''\ 2 Entry 1 Entry 2 ''') tree_invalid = self.parse('''\ 1 Entry 1 Entry 2 ''') xmlschema = etree.XMLSchema(schema) schematron = isoschematron.Schematron(schema) # fwiw, this must also be XMLSchema-valid self.assertTrue(xmlschema(tree_valid), xmlschema.error_log) self.assertTrue(schematron(tree_valid)) # still schema-valid self.assertTrue(xmlschema(tree_invalid), xmlschema.error_log) self.assertTrue(not schematron(tree_invalid)) def test_schematron_relaxng_embedded(self): schema = self.parse('''\ mandatory number_of_entries tests [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') tree_valid = self.parse('''\ 2 Entry 1 Entry 2 ''') tree_invalid = self.parse('''\ 1 Entry 1 Entry 2 ''') relaxng = etree.RelaxNG(schema) schematron = isoschematron.Schematron(schema) # fwiw, this must also be RelaxNG-valid self.assertTrue(relaxng(tree_valid), relaxng.error_log) self.assertTrue(schematron(tree_valid)) # still schema-valid self.assertTrue(relaxng(tree_invalid), relaxng.error_log) self.assertTrue(not schematron(tree_invalid)) def test_schematron_invalid_args(self): schema = self.parse('''\ mandatory number_of_entries tests [ERROR] number_of_entries () must equal the number of entries/entry elements () ''') # handing phase as keyword arg will *not* raise the type error self.assertRaises(TypeError, isoschematron.Schematron, schema, compile_params={'phase': None}) def test_schematron_customization(self): class MySchematron(isoschematron.Schematron): def _extract(self, root): schematron = (root.xpath( '//sch:schema', namespaces={'sch': "http://purl.oclc.org/dsdl/schematron"}) or [None])[0] return schematron def _include(self, schematron, **kwargs): raise RuntimeError('inclusion unsupported') def _expand(self, schematron, **kwargs): raise RuntimeError('expansion unsupported') def _validation_errors(self, validationReport): valid = etree.XPath( 'count(//svrl:successful-report[@flag="critical"])=1', namespaces={'svrl': isoschematron.SVRL_NS})( validationReport) if valid: return [] error = etree.Element('Error') error.text = 'missing critical condition report' return [error] tree_valid = self.parse('') tree_invalid = self.parse('') schema = self.parse('''\ Open Model BBB element must be present CCC element must be present Closed model" BBB element must be present CCC element must be present Only BBB and CCC children must be present ''') # check if overridden _include is run self.assertRaises(RuntimeError, MySchematron, schema, store_report=True) # check if overridden _expand is run self.assertRaises(RuntimeError, MySchematron, schema, store_report=True, include=False) schema = MySchematron(schema, store_report=True, include=False, expand=False) self.assertTrue(schema.validate(tree_valid)) self.assertTrue(not schema.validate(tree_invalid)) #TODO: test xslt parameters for inclusion, expand & compile steps (?) def test_schematron_fail_on_report(self): tree_valid = self.parse('') tree_invalid = self.parse('') schema = self.parse('''\ Simple Report DDD element must not be present ''') schema_report = isoschematron.Schematron( schema, error_finder=isoschematron.Schematron.ASSERTS_AND_REPORTS) schema_no_report = isoschematron.Schematron(schema) self.assertTrue(schema_report.validate(tree_valid)) self.assertTrue(not schema_report.validate(tree_invalid)) self.assertTrue(schema_no_report.validate(tree_valid)) self.assertTrue(schema_no_report.validate(tree_invalid)) def test_suite(): suite = unittest.TestSuite() suite.addTests([unittest.makeSuite(ETreeISOSchematronTestCase)]) suite.addTests(doctest.DocTestSuite(isoschematron)) suite.addTests( [make_doctest('../../../doc/validation.txt')]) return suite if __name__ == '__main__': print('to test use test.py %s' % __file__)