lxml.tests.test_isoschematron

Package lxml :: Package tests :: Module test_isoschematron |
[hide private]
[frames] | no frames]

# Source Code for Module lxml.tests.test_isoschematron

      1  # -*- coding: utf-8 -*-
      2
      3  """
      4  Test cases related to ISO-Schematron parsing and validation
      5  """
      6
      7  from __future__ import absolute_import
      8
      9  import unittest
     10  from lxml import isoschematron
     11
     12  from .common_imports import etree, HelperTestCase, fileInTestDir, doctest, make_doctest
     13
     14
     15 -class ETreeISOSchematronTestCase(HelperTestCase):
     16 -    def test_schematron(self):
     17          tree_valid = self.parse('<AAA><BBB/><CCC/></AAA>')
     18          tree_invalid = self.parse('<AAA><BBB/><CCC/><DDD/></AAA>')
     19          schema = self.parse('''\
     20  <schema xmlns="http://purl.oclc.org/dsdl/schematron" >
     21      <pattern id="OpenModel">
     22          <title>Open Model</title>
     23          <rule context="AAA">
     24              <assert test="BBB"> BBB element is not present</assert>
     25              <assert test="CCC"> CCC element is not present</assert>
     26          </rule>
     27      </pattern>
     28      <pattern id="ClosedModel">
     29          <title>Closed model"</title>
     30          <rule context="AAA">
     31              <assert test="BBB"> BBB element is not present</assert>
     32              <assert test="CCC"> CCC element is not present</assert>
     33              <assert test="count(BBB|CCC) = count (*)">There is an extra element</assert>
     34          </rule>
     35      </pattern>
     36  </schema>
     37  ''')
     38
     39          schema = isoschematron.Schematron(schema)
     40          self.assertTrue(schema.validate(tree_valid))
     41          self.assertTrue(not schema.validate(tree_invalid))
     42
     43 -    def test_schematron_elementtree_error(self):
     44          self.assertRaises(ValueError, isoschematron.Schematron, etree.ElementTree())
     45
     46      # an empty pattern is valid in iso schematron
     47 -    def test_schematron_empty_pattern(self):
     48          schema = self.parse('''\
     49  <schema xmlns="http://purl.oclc.org/dsdl/schematron" >
     50      <pattern id="OpenModel">
     51          <title>Open model</title>
     52      </pattern>
     53  </schema>
     54  ''')
     55          schema = isoschematron.Schematron(schema)
     56          self.assertTrue(schema)
     57
     58 -    def test_schematron_invalid_schema_empty(self):
     59          schema = self.parse('''\
     60  <schema xmlns="http://purl.oclc.org/dsdl/schematron" />
     61  ''')
     62          self.assertRaises(etree.SchematronParseError,
     63                            isoschematron.Schematron, schema)
     64
     65 -    def test_schematron_invalid_schema_namespace(self):
     66          schema = self.parse('''\
     67  <schema xmlns="mynamespace" />
     68  ''')
     69          self.assertRaises(etree.SchematronParseError,
     70                            isoschematron.Schematron, schema)
     71
     72 -    def test_schematron_from_tree(self):
     73          schema = self.parse('''\
     74  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
     75    <sch:pattern id="number_of_entries">
     76      <sch:title>mandatory number_of_entries tests</sch:title>
     77      <sch:rule context="number_of_entries">
     78        <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
     79      </sch:rule>
     80    </sch:pattern>
     81  </sch:schema>
     82  ''')
     83          schematron = isoschematron.Schematron(schema)
     84          self.assertTrue(isinstance(schematron, isoschematron.Schematron))
     85
     86 -    def test_schematron_from_element(self):
     87          schema = self.parse('''\
     88  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
     89    <sch:pattern id="number_of_entries">
     90      <sch:title>mandatory number_of_entries tests</sch:title>
     91      <sch:rule context="number_of_entries">
     92        <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
     93      </sch:rule>
     94    </sch:pattern>
     95  </sch:schema>
     96  ''')
     97          schematron = isoschematron.Schematron(schema.getroot())
     98          self.assertTrue(isinstance(schematron, isoschematron.Schematron))
     99
    100 -    def test_schematron_from_file(self):
    101          schematron = isoschematron.Schematron(file=fileInTestDir('test.sch'))
    102          self.assertTrue(isinstance(schematron, isoschematron.Schematron))
    103
    104 -    def test_schematron_call(self):
    105          schema = self.parse('''\
    106  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    107    <sch:pattern id="number_of_entries">
    108      <sch:title>mandatory number_of_entries tests</sch:title>
    109      <sch:rule context="number_of_entries">
    110        <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
    111      </sch:rule>
    112    </sch:pattern>
    113  </sch:schema>
    114  ''')
    115          tree_valid = self.parse('''\
    116  <message>
    117    <number_of_entries>0</number_of_entries>
    118    <entries>
    119    </entries>
    120  </message>
    121  ''')
    122          tree_invalid = self.parse('''\
    123  <message>
    124    <number_of_entries>3</number_of_entries>
    125    <entries>
    126      <entry>Entry 1</entry>
    127      <entry>Entry 2</entry>
    128    </entries>
    129  </message>
    130  ''')
    131          schematron = isoschematron.Schematron(schema)
    132          self.assertTrue(schematron(tree_valid), schematron.error_log)
    133          valid = schematron(tree_invalid)
    134          self.assertTrue(not valid)
    135
    136 -    def test_schematron_validate(self):
    137          schema = self.parse('''\
    138  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    139    <sch:pattern id="number_of_entries">
    140      <sch:title>mandatory number_of_entries tests</sch:title>
    141      <sch:rule context="number_of_entries">
    142        <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
    143      </sch:rule>
    144    </sch:pattern>
    145  </sch:schema>
    146  ''')
    147          tree_valid = self.parse('''\
    148  <message>
    149    <number_of_entries>0</number_of_entries>
    150    <entries>
    151    </entries>
    152  </message>
    153  ''')
    154          tree_invalid = self.parse('''\
    155  <message>
    156    <number_of_entries>3</number_of_entries>
    157    <entries>
    158      <entry>Entry 1</entry>
    159      <entry>Entry 2</entry>
    160    </entries>
    161  </message>
    162  ''')
    163          schematron = isoschematron.Schematron(schema)
    164          self.assertTrue(schematron.validate(tree_valid), schematron.error_log)
    165          valid = schematron.validate(tree_invalid)
    166          self.assertTrue(not valid)
    167
    168 -    def test_schematron_assertValid(self):
    169          schema = self.parse('''\
    170  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    171    <sch:pattern id="number_of_entries">
    172      <sch:title>mandatory number_of_entries tests</sch:title>
    173      <sch:rule context="number_of_entries">
    174        <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
    175      </sch:rule>
    176    </sch:pattern>
    177  </sch:schema>
    178  ''')
    179          tree_valid = self.parse('''\
    180  <message>
    181    <number_of_entries>0</number_of_entries>
    182    <entries>
    183    </entries>
    184  </message>
    185  ''')
    186          tree_invalid = self.parse('''\
    187  <message>
    188    <number_of_entries>3</number_of_entries>
    189    <entries>
    190      <entry>Entry 1</entry>
    191      <entry>Entry 2</entry>
    192    </entries>
    193  </message>
    194  ''')
    195          schematron = isoschematron.Schematron(schema)
    196          self.assertTrue(schematron(tree_valid), schematron.error_log)
    197          self.assertRaises(etree.DocumentInvalid, schematron.assertValid,
    198                            tree_invalid)
    199
    200 -    def test_schematron_error_log(self):
    201          schema = self.parse('''\
    202  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    203    <sch:pattern id="number_of_entries">
    204      <sch:title>mandatory number_of_entries tests</sch:title>
    205      <sch:rule context="number_of_entries">
    206        <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
    207      </sch:rule>
    208    </sch:pattern>
    209  </sch:schema>
    210  ''')
    211          tree_valid = self.parse('''\
    212  <message>
    213    <number_of_entries>0</number_of_entries>
    214    <entries>
    215    </entries>
    216  </message>
    217  ''')
    218          tree_invalid = self.parse('''\
    219  <message>
    220    <number_of_entries>3</number_of_entries>
    221    <entries>
    222      <entry>Entry 1</entry>
    223      <entry>Entry 2</entry>
    224    </entries>
    225  </message>
    226  ''')
    227          schematron = isoschematron.Schematron(schema)
    228          self.assertTrue(schematron(tree_valid), schematron.error_log)
    229          valid = schematron(tree_invalid)
    230          self.assertTrue(not valid)
    231          self.assertEqual(len(schematron.error_log), 1,
    232                            'expected single error: %s (%s errors)' %
    233                            (schematron.error_log, len(schematron.error_log)))
    234
    235 -    def test_schematron_result_report(self):
    236          schema = self.parse('''\
    237  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    238    <sch:pattern id="number_of_entries">
    239      <sch:title>mandatory number_of_entries tests</sch:title>
    240      <sch:rule context="number_of_entries">
    241        <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
    242      </sch:rule>
    243    </sch:pattern>
    244  </sch:schema>
    245  ''')
    246          tree_valid = self.parse('''\
    247  <message>
    248    <number_of_entries>0</number_of_entries>
    249    <entries>
    250    </entries>
    251  </message>
    252  ''')
    253          tree_invalid = self.parse('''\
    254  <message>
    255    <number_of_entries>3</number_of_entries>
    256    <entries>
    257      <entry>Entry 1</entry>
    258      <entry>Entry 2</entry>
    259    </entries>
    260  </message>
    261  ''')
    262          schematron = isoschematron.Schematron(schema, store_report=True)
    263          self.assertTrue(schematron(tree_valid), schematron.error_log)
    264          valid = schematron(tree_invalid)
    265          self.assertTrue(not valid)
    266          self.assertTrue(
    267              isinstance(schematron.validation_report, etree._ElementTree),
    268              'expected a validation report result tree, got: %s' % schematron.validation_report)
    269
    270          schematron = isoschematron.Schematron(schema, store_report=False)
    271          self.assertTrue(schematron(tree_valid), schematron.error_log)
    272          valid = schematron(tree_invalid)
    273          self.assertTrue(not valid)
    274          self.assertTrue(schematron.validation_report is None,
    275              'validation reporting switched off, still: %s' % schematron.validation_report)
    276
    277 -    def test_schematron_store_schematron(self):
    278          schema = self.parse('''\
    279  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    280    <sch:pattern id="number_of_entries">
    281      <sch:title>mandatory number_of_entries tests</sch:title>
    282      <sch:rule context="number_of_entries">
    283        <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
    284      </sch:rule>
    285    </sch:pattern>
    286  </sch:schema>
    287  ''')
    288          schematron = isoschematron.Schematron(schema)
    289          self.assertTrue(schematron.validator_xslt is None)
    290
    291          schematron = isoschematron.Schematron(schema, store_schematron=True)
    292          self.assertTrue(isinstance(schematron.schematron, etree._ElementTree),
    293                       'expected schematron schema to be stored')
    294
    295 -    def test_schematron_store_xslt(self):
    296          schema = self.parse('''\
    297  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    298    <sch:pattern id="number_of_entries">
    299      <sch:title>mandatory number_of_entries tests</sch:title>
    300      <sch:rule context="number_of_entries">
    301        <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
    302      </sch:rule>
    303    </sch:pattern>
    304  </sch:schema>
    305  ''')
    306          schematron = isoschematron.Schematron(schema)
    307          self.assertTrue(schematron.validator_xslt is None)
    308
    309          schematron = isoschematron.Schematron(schema, store_xslt=True)
    310          self.assertTrue(isinstance(schematron.validator_xslt, etree._ElementTree),
    311                       'expected validator xslt to be stored')
    312
    313 -    def test_schematron_abstract(self):
    314          schema = self.parse('''\
    315  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    316    <sch:title>iso schematron validation</sch:title>
    317    <sch:ns uri="http://www.w3.org/2001/XMLSchema-instance" prefix="xsi"/>
    318    <sch:ns uri="http://codespeak.net/lxml/objectify/pytype" prefix="py"/>
    319
    320    <!-- of course, these only really make sense when combined with a schema that
    321         ensures datatype xs:dateTime -->
    322
    323    <sch:pattern abstract="true" id="abstract.dateTime.tz_utc">
    324      <sch:rule context="$datetime">
    325        <sch:let name="tz" value="concat(substring-after(substring-after(./text(), 'T'), '+'), substring-after(substring-after(./text(), 'T'), '-'))"/>
    326        <sch:let name="lastchar" value="substring(./text(), string-length(./text()))"/>
    327        <sch:assert test="$lastchar='Z' or $tz='00:00'">[ERROR] element (<sch:value-of select="name(.)"/>) dateTime value (<sch:value-of select="."/>) is not qualified as UTC (tz: <sch:value-of select="$tz"/>)</sch:assert>
    328      </sch:rule>
    329    </sch:pattern>
    330
    331    <sch:pattern abstract="true" id="abstract.dateTime.tz_utc_nillable">
    332      <sch:rule context="$datetime">
    333        <sch:let name="tz" value="concat(substring-after(substring-after(./text(), 'T'), '+'), substring-after(substring-after(./text(), 'T'), '-'))"/>
    334        <sch:let name="lastchar" value="substring(./text(), string-length(./text()))"/>
    335        <sch:assert test="@xsi:nil='true'  or ($lastchar='Z' or $tz='00:00')">[ERROR] element (<sch:value-of select="name(.)"/>) dateTime value (<sch:value-of select="."/>) is not qualified as UTC (tz: <sch:value-of select="$tz"/>)</sch:assert>
    336      </sch:rule>
    337    </sch:pattern>
    338
    339    <sch:pattern is-a="abstract.dateTime.tz_utc" id="datetime" >
    340      <sch:param name="datetime" value="datetime"/>
    341    </sch:pattern>
    342
    343    <sch:pattern is-a="abstract.dateTime.tz_utc_nillable" id="nillableDatetime">
    344      <sch:param name="datetime" value="nillableDatetime"/>
    345    </sch:pattern>
    346
    347  </sch:schema>
    348  ''')
    349          valid_trees = [
    350              self.parse('''\
    351  <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    352    <datetime>2009-12-10T15:21:00Z</datetime>
    353    <nillableDatetime xsi:nil="true"/>
    354  </root>
    355  '''),
    356              self.parse('''\
    357  <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    358    <datetime>2009-12-10T15:21:00Z</datetime>
    359    <nillableDatetime>2009-12-10T15:21:00Z</nillableDatetime>
    360  </root>
    361  '''),
    362              self.parse('''\
    363  <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    364    <datetime>2009-12-10T15:21:00+00:00</datetime>
    365    <nillableDatetime>2009-12-10T15:21:00-00:00</nillableDatetime>
    366  </root>
    367  '''),
    368              ]
    369
    370          schematron = isoschematron.Schematron(schema)
    371          for tree_valid in valid_trees:
    372              self.assertTrue(schematron(tree_valid), schematron.error_log)
    373
    374          tree_invalid = self.parse('''\
    375  <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    376    <datetime>2009-12-10T16:21:00+01:00</datetime>
    377    <nillableDatetime>2009-12-10T16:21:00+01:00</nillableDatetime>
    378  </root>
    379  ''')
    380          expected = 2
    381          valid = schematron(tree_invalid)
    382          self.assertTrue(not valid)
    383          self.assertEqual(
    384              len(schematron.error_log), expected,
    385              'expected %s errors: %s (%s errors)' %
    386              (expected, schematron.error_log, len(schematron.error_log)))
    387
    388          tree_invalid = self.parse('''\
    389  <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    390    <datetime xsi:nil="true"/>
    391    <nillableDatetime>2009-12-10T16:21:00Z</nillableDatetime>
    392  </root>
    393  ''')
    394          expected = 1
    395          valid = schematron(tree_invalid)
    396          self.assertTrue(not valid)
    397          self.assertEqual(
    398              len(schematron.error_log), expected,
    399              'expected %s errors: %s (%s errors)' %
    400              (expected, schematron.error_log, len(schematron.error_log)))
    401
    402 -    def test_schematron_phases(self):
    403          schema = self.parse('''\
    404  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    405    <sch:title>iso schematron validation</sch:title>
    406    <sch:ns uri="http://www.w3.org/2001/XMLSchema-instance" prefix="xsi"/>
    407    <sch:ns uri="http://codespeak.net/lxml/objectify/pytype" prefix="py"/>
    408
    409    <sch:phase id="mandatory">
    410      <sch:active pattern="number_of_entries"/>
    411    </sch:phase>
    412
    413    <sch:phase id="datetime_checks">
    414      <sch:active pattern="datetime"/>
    415      <sch:active pattern="nillableDatetime"/>
    416    </sch:phase>
    417
    418    <sch:phase id="full">
    419      <sch:active pattern="number_of_entries"/>
    420      <sch:active pattern="datetime"/>
    421      <sch:active pattern="nillableDatetime"/>
    422    </sch:phase>
    423
    424    <!-- of course, these only really make sense when combined with a schema that
    425         ensures datatype xs:dateTime -->
    426
    427    <sch:pattern abstract="true" id="abstract.dateTime.tz_utc">
    428      <sch:rule context="$datetime">
    429        <sch:let name="tz" value="concat(substring-after(substring-after(./text(), 'T'), '+'), substring-after(substring-after(./text(), 'T'), '-'))"/>
    430        <sch:let name="lastchar" value="substring(./text(), string-length(./text()))"/>
    431        <sch:assert test="$lastchar='Z' or $tz='00:00'">[ERROR] element (<sch:value-of select="name(.)"/>) dateTime value (<sch:value-of select="."/>) is not qualified as UTC (tz: <sch:value-of select="$tz"/>)</sch:assert>
    432      </sch:rule>
    433    </sch:pattern>
    434
    435    <sch:pattern abstract="true" id="abstract.dateTime.tz_utc_nillable">
    436      <sch:rule context="$datetime">
    437        <sch:let name="tz" value="concat(substring-after(substring-after(./text(), 'T'), '+'), substring-after(substring-after(./text(), 'T'), '-'))"/>
    438        <sch:let name="lastchar" value="substring(./text(), string-length(./text()))"/>
    439        <sch:assert test="@xsi:nil='true'  or ($lastchar='Z' or $tz='00:00')">[ERROR] element (<sch:value-of select="name(.)"/>) dateTime value (<sch:value-of select="."/>) is not qualified as UTC (tz: <sch:value-of select="$tz"/>)</sch:assert>
    440      </sch:rule>
    441    </sch:pattern>
    442
    443    <sch:pattern id="number_of_entries">
    444      <sch:title>mandatory number_of_entries test</sch:title>
    445      <sch:rule context="number_of_entries">
    446        <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
    447      </sch:rule>
    448    </sch:pattern>
    449
    450    <sch:pattern  id="datetime" is-a="abstract.dateTime.tz_utc">
    451      <sch:param name="datetime" value="datetime"/>
    452    </sch:pattern>
    453
    454    <sch:pattern  id="nillableDatetime" is-a="abstract.dateTime.tz_utc_nillable">
    455      <sch:param name="datetime" value="nillableDatetime"/>
    456    </sch:pattern>
    457
    458  </sch:schema>
    459  ''')
    460          tree_valid = self.parse('''\
    461  <message xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    462    <datetime>2009-12-10T15:21:00Z</datetime>
    463    <nillableDatetime xsi:nil="true"/>
    464    <number_of_entries>0</number_of_entries>
    465    <entries>
    466    </entries>
    467  </message>
    468  ''')
    469          tree_invalid = self.parse('''\
    470  <message>
    471    <datetime>2009-12-10T16:21:00+01:00</datetime>
    472    <nillableDatetime>2009-12-10T16:21:00+01:00</nillableDatetime>
    473    <number_of_entries>3</number_of_entries>
    474    <entries>
    475      <entry>Entry 1</entry>
    476      <entry>Entry 2</entry>
    477    </entries>
    478  </message>
    479  ''')
    480          # check everything (default phase #ALL)
    481          schematron = isoschematron.Schematron(schema)
    482          self.assertTrue(schematron(tree_valid), schematron.error_log)
    483          expected = 3
    484          valid = schematron(tree_invalid)
    485          self.assertTrue(not valid)
    486          self.assertEqual(
    487              len(schematron.error_log), expected,
    488              'expected %s errors: %s (%s errors)' %
    489              (expected, schematron.error_log, len(schematron.error_log)))
    490
    491          # check phase mandatory
    492          schematron = isoschematron.Schematron(
    493              schema, compile_params={'phase': 'mandatory'})
    494          self.assertTrue(schematron(tree_valid), schematron.error_log)
    495          expected = 1
    496          valid = schematron(tree_invalid)
    497          self.assertTrue(not valid)
    498          self.assertEqual(
    499              len(schematron.error_log), expected,
    500              'expected %s errors: %s (%s errors)' %
    501              (expected, schematron.error_log, len(schematron.error_log)))
    502
    503          # check phase datetime_checks
    504          schematron = isoschematron.Schematron(
    505              schema, compile_params={'phase': 'datetime_checks'})
    506          self.assertTrue(schematron(tree_valid), schematron.error_log)
    507          expected = 2
    508          valid = schematron(tree_invalid)
    509          self.assertTrue(not valid)
    510          self.assertEqual(
    511              len(schematron.error_log), expected,
    512              'expected %s errors: %s (%s errors)' %
    513              (expected, schematron.error_log, len(schematron.error_log)))
    514
    515          # check phase full
    516          schematron = isoschematron.Schematron(
    517              schema, compile_params={'phase': 'full'})
    518          self.assertTrue(schematron(tree_valid), schematron.error_log)
    519          expected = 3
    520          valid = schematron(tree_invalid)
    521          self.assertTrue(not valid)
    522          self.assertEqual(
    523              len(schematron.error_log), expected,
    524              'expected %s errors: %s (%s errors)' %
    525              (expected, schematron.error_log, len(schematron.error_log)))
    526
    527 -    def test_schematron_phases_kwarg(self):
    528          schema = self.parse('''\
    529  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    530    <sch:title>iso schematron validation</sch:title>
    531    <sch:ns uri="http://www.w3.org/2001/XMLSchema-instance" prefix="xsi"/>
    532    <sch:ns uri="http://codespeak.net/lxml/objectify/pytype" prefix="py"/>
    533
    534    <sch:phase id="mandatory">
    535      <sch:active pattern="number_of_entries"/>
    536    </sch:phase>
    537
    538    <sch:phase id="datetime_checks">
    539      <sch:active pattern="datetime"/>
    540      <sch:active pattern="nillableDatetime"/>
    541    </sch:phase>
    542
    543    <sch:phase id="full">
    544      <sch:active pattern="number_of_entries"/>
    545      <sch:active pattern="datetime"/>
    546      <sch:active pattern="nillableDatetime"/>
    547    </sch:phase>
    548
    549    <!-- of course, these only really make sense when combined with a schema that
    550         ensures datatype xs:dateTime -->
    551
    552    <sch:pattern abstract="true" id="abstract.dateTime.tz_utc">
    553      <sch:rule context="$datetime">
    554        <sch:let name="tz" value="concat(substring-after(substring-after(./text(), 'T'), '+'), substring-after(substring-after(./text(), 'T'), '-'))"/>
    555        <sch:let name="lastchar" value="substring(./text(), string-length(./text()))"/>
    556        <sch:assert test="$lastchar='Z' or $tz='00:00'">[ERROR] element (<sch:value-of select="name(.)"/>) dateTime value (<sch:value-of select="."/>) is not qualified as UTC (tz: <sch:value-of select="$tz"/>)</sch:assert>
    557      </sch:rule>
    558    </sch:pattern>
    559
    560    <sch:pattern abstract="true" id="abstract.dateTime.tz_utc_nillable">
    561      <sch:rule context="$datetime">
    562        <sch:let name="tz" value="concat(substring-after(substring-after(./text(), 'T'), '+'), substring-after(substring-after(./text(), 'T'), '-'))"/>
    563        <sch:let name="lastchar" value="substring(./text(), string-length(./text()))"/>
    564        <sch:assert test="@xsi:nil='true'  or ($lastchar='Z' or $tz='00:00')">[ERROR] element (<sch:value-of select="name(.)"/>) dateTime value (<sch:value-of select="."/>) is not qualified as UTC (tz: <sch:value-of select="$tz"/>)</sch:assert>
    565      </sch:rule>
    566    </sch:pattern>
    567
    568    <sch:pattern id="number_of_entries">
    569      <sch:title>mandatory number_of_entries test</sch:title>
    570      <sch:rule context="number_of_entries">
    571        <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
    572      </sch:rule>
    573    </sch:pattern>
    574
    575    <sch:pattern  id="datetime" is-a="abstract.dateTime.tz_utc">
    576      <sch:param name="datetime" value="datetime"/>
    577    </sch:pattern>
    578
    579    <sch:pattern  id="nillableDatetime" is-a="abstract.dateTime.tz_utc_nillable">
    580      <sch:param name="datetime" value="nillableDatetime"/>
    581    </sch:pattern>
    582
    583  </sch:schema>
    584  ''')
    585          tree_valid = self.parse('''\
    586  <message xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    587    <datetime>2009-12-10T15:21:00Z</datetime>
    588    <nillableDatetime xsi:nil="true"/>
    589    <number_of_entries>0</number_of_entries>
    590    <entries>
    591    </entries>
    592  </message>
    593  ''')
    594          tree_invalid = self.parse('''\
    595  <message>
    596    <datetime>2009-12-10T16:21:00+01:00</datetime>
    597    <nillableDatetime>2009-12-10T16:21:00+01:00</nillableDatetime>
    598    <number_of_entries>3</number_of_entries>
    599    <entries>
    600      <entry>Entry 1</entry>
    601      <entry>Entry 2</entry>
    602    </entries>
    603  </message>
    604  ''')
    605          # check everything (default phase #ALL)
    606          schematron = isoschematron.Schematron(schema)
    607          self.assertTrue(schematron(tree_valid), schematron.error_log)
    608          expected = 3
    609          valid = schematron(tree_invalid)
    610          self.assertTrue(not valid)
    611          self.assertEqual(
    612              len(schematron.error_log), expected,
    613              'expected %s errors: %s (%s errors)' %
    614              (expected, schematron.error_log, len(schematron.error_log)))
    615
    616          # check phase mandatory
    617          schematron = isoschematron.Schematron(schema, phase='mandatory')
    618          self.assertTrue(schematron(tree_valid), schematron.error_log)
    619          expected = 1
    620          valid = schematron(tree_invalid)
    621          self.assertTrue(not valid)
    622          self.assertEqual(
    623              len(schematron.error_log), expected,
    624              'expected %s errors: %s (%s errors)' %
    625              (expected, schematron.error_log, len(schematron.error_log)))
    626
    627          # check phase datetime_checks
    628          schematron = isoschematron.Schematron(schema, phase='datetime_checks')
    629          self.assertTrue(schematron(tree_valid), schematron.error_log)
    630          expected = 2
    631          valid = schematron(tree_invalid)
    632          self.assertTrue(not valid)
    633          self.assertEqual(
    634              len(schematron.error_log), expected,
    635              'expected %s errors: %s (%s errors)' %
    636              (expected, schematron.error_log, len(schematron.error_log)))
    637
    638          # check phase full
    639          schematron = isoschematron.Schematron(schema, phase='full')
    640          self.assertTrue(schematron(tree_valid), schematron.error_log)
    641          expected = 3
    642          valid = schematron(tree_invalid)
    643          self.assertTrue(not valid)
    644          self.assertEqual(
    645              len(schematron.error_log), expected, 'expected %s errors: %s (%s errors)' %
    646              (expected, schematron.error_log, len(schematron.error_log)))
    647
    648 -    def test_schematron_xmlschema_embedded(self):
    649          schema = self.parse('''\
    650  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    651      xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    652      <xs:element name="message">
    653          <xs:complexType>
    654              <xs:sequence>
    655                  <xs:element name="number_of_entries" type="xs:positiveInteger">
    656                      <xs:annotation>
    657                          <xs:appinfo>
    658                              <sch:pattern id="number_of_entries">
    659                                  <sch:title>mandatory number_of_entries tests</sch:title>
    660                                  <sch:rule context="number_of_entries">
    661                                      <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
    662                                  </sch:rule>
    663                              </sch:pattern>
    664                          </xs:appinfo>
    665                      </xs:annotation>
    666                  </xs:element>
    667                  <xs:element name="entries">
    668                      <xs:complexType>
    669                          <xs:sequence>
    670                              <xs:element name="entry" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
    671                          </xs:sequence>
    672                      </xs:complexType>
    673                  </xs:element>
    674              </xs:sequence>
    675          </xs:complexType>
    676      </xs:element>
    677  </xs:schema>
    678  ''')
    679          tree_valid = self.parse('''\
    680  <message>
    681    <number_of_entries>2</number_of_entries>
    682    <entries>
    683      <entry>Entry 1</entry>
    684      <entry>Entry 2</entry>
    685    </entries>
    686  </message>
    687  ''')
    688          tree_invalid = self.parse('''\
    689  <message>
    690    <number_of_entries>1</number_of_entries>
    691    <entries>
    692      <entry>Entry 1</entry>
    693      <entry>Entry 2</entry>
    694    </entries>
    695  </message>
    696  ''')
    697          xmlschema = etree.XMLSchema(schema)
    698          schematron = isoschematron.Schematron(schema)
    699          # fwiw, this must also be XMLSchema-valid
    700          self.assertTrue(xmlschema(tree_valid), xmlschema.error_log)
    701          self.assertTrue(schematron(tree_valid))
    702          # still schema-valid
    703          self.assertTrue(xmlschema(tree_invalid), xmlschema.error_log)
    704          self.assertTrue(not schematron(tree_invalid))
    705
    706 -    def test_schematron_relaxng_embedded(self):
    707          schema = self.parse('''\
    708  <grammar xmlns="http://relaxng.org/ns/structure/1.0"
    709    xmlns:sch="http://purl.oclc.org/dsdl/schematron"
    710    datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
    711    <start>
    712      <ref name="message"/>
    713    </start>
    714    <define name="message">
    715      <element name="message">
    716        <element name="number_of_entries">
    717          <!-- RelaxNG can be mixed freely with stuff from other namespaces -->
    718          <sch:pattern id="number_of_entries">
    719            <sch:title>mandatory number_of_entries tests</sch:title>
    720            <sch:rule context="number_of_entries">
    721              <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
    722            </sch:rule>
    723          </sch:pattern>
    724          <data type="positiveInteger"/>
    725        </element>
    726        <element name="entries">
    727          <zeroOrMore>
    728            <element name="entry"><data type="string"/></element>
    729          </zeroOrMore>
    730        </element>
    731      </element>
    732    </define>
    733  </grammar>
    734  ''')
    735          tree_valid = self.parse('''\
    736  <message>
    737    <number_of_entries>2</number_of_entries>
    738    <entries>
    739      <entry>Entry 1</entry>
    740      <entry>Entry 2</entry>
    741    </entries>
    742  </message>
    743  ''')
    744          tree_invalid = self.parse('''\
    745  <message>
    746    <number_of_entries>1</number_of_entries>
    747    <entries>
    748      <entry>Entry 1</entry>
    749      <entry>Entry 2</entry>
    750    </entries>
    751  </message>
    752  ''')
    753          relaxng = etree.RelaxNG(schema)
    754          schematron = isoschematron.Schematron(schema)
    755          # fwiw, this must also be RelaxNG-valid
    756          self.assertTrue(relaxng(tree_valid), relaxng.error_log)
    757          self.assertTrue(schematron(tree_valid))
    758          # still schema-valid
    759          self.assertTrue(relaxng(tree_invalid), relaxng.error_log)
    760          self.assertTrue(not schematron(tree_invalid))
    761
    762 -    def test_schematron_invalid_args(self):
    763          schema = self.parse('''\
    764  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    765    <sch:pattern id="number_of_entries">
    766      <sch:title>mandatory number_of_entries tests</sch:title>
    767      <sch:rule context="number_of_entries">
    768        <sch:assert test="text()=count(../entries/entry)">[ERROR] number_of_entries (<sch:value-of select="."/>) must equal the number of entries/entry elements (<sch:value-of select="count(../entries/entry)"/>)</sch:assert>
    769      </sch:rule>
    770    </sch:pattern>
    771  </sch:schema>
    772  ''')
    773          # handing phase as keyword arg will *not* raise the type error
    774          self.assertRaises(TypeError, isoschematron.Schematron, schema,
    775                            compile_params={'phase': None})
    776
    777 -    def test_schematron_customization(self):
    778          class MySchematron(isoschematron.Schematron):
    779              def _extract(self, root):
    780                  schematron = (root.xpath(
    781                      '//sch:schema',
    782                      namespaces={'sch': "http://purl.oclc.org/dsdl/schematron"})
    783                      or [None])[0]
    784                  return schematron
    785
    786              def _include(self, schematron, **kwargs):
    787                  raise RuntimeError('inclusion unsupported')
    788
    789              def _expand(self, schematron, **kwargs):
    790                  raise RuntimeError('expansion unsupported')
    791
    792              def _validation_errors(self, validationReport):
    793                  valid = etree.XPath(
    794                      'count(//svrl:successful-report[@flag="critical"])=1',
    795                      namespaces={'svrl': isoschematron.SVRL_NS})(
    796                      validationReport)
    797                  if valid:
    798                      return []
    799                  error = etree.Element('Error')
    800                  error.text = 'missing critical condition report'
    801                  return [error]
    802
    803          tree_valid = self.parse('<AAA><BBB/><CCC/></AAA>')
    804          tree_invalid = self.parse('<AAA><BBB/><CCC/><DDD/></AAA>')
    805          schema = self.parse('''\
    806  <schema xmlns="http://www.example.org/yet/another/schema/dialect">
    807    <schema xmlns="http://purl.oclc.org/dsdl/schematron" >
    808      <pattern id="OpenModel">
    809        <title>Open Model</title>
    810        <rule context="AAA">
    811          <report test="BBB" flag="info">BBB element must be present</report>
    812          <report test="CCC" flag="info">CCC element must be present</report>
    813        </rule>
    814      </pattern>
    815      <pattern id="ClosedModel">
    816        <title>Closed model"</title>
    817        <rule context="AAA">
    818          <report test="BBB" flag="info">BBB element must be present</report>
    819          <report test="CCC" flag="info">CCC element must be present</report>
    820          <report test="count(BBB|CCC) = count(*)" flag="critical">Only BBB and CCC children must be present</report>
    821        </rule>
    822      </pattern>
    823    </schema>
    824  </schema>
    825  ''')
    826          # check if overridden _include is run
    827          self.assertRaises(RuntimeError, MySchematron, schema, store_report=True)
    828          # check if overridden _expand is run
    829          self.assertRaises(RuntimeError, MySchematron, schema, store_report=True,
    830                            include=False)
    831
    832          schema = MySchematron(schema, store_report=True, include=False,
    833                                expand=False)
    834          self.assertTrue(schema.validate(tree_valid))
    835          self.assertTrue(not schema.validate(tree_invalid))
    836
    837      #TODO: test xslt parameters for inclusion, expand & compile steps (?)
    838
    839 -    def test_schematron_fail_on_report(self):
    840          tree_valid = self.parse('<AAA><BBB/><CCC/></AAA>')
    841          tree_invalid = self.parse('<AAA><BBB/><CCC/><DDD/></AAA>')
    842          schema = self.parse('''\
    843  <schema xmlns="http://purl.oclc.org/dsdl/schematron" >
    844      <pattern id="OpenModel">
    845          <title>Simple Report</title>
    846          <rule context="AAA">
    847              <report test="DDD"> DDD element must not be present</report>
    848          </rule>
    849      </pattern>
    850  </schema>
    851  ''')
    852          schema_report = isoschematron.Schematron(
    853              schema, error_finder=isoschematron.Schematron.ASSERTS_AND_REPORTS)
    854          schema_no_report = isoschematron.Schematron(schema)
    855          self.assertTrue(schema_report.validate(tree_valid))
    856          self.assertTrue(not schema_report.validate(tree_invalid))
    857          self.assertTrue(schema_no_report.validate(tree_valid))
    858          self.assertTrue(schema_no_report.validate(tree_invalid))
    859
    860
    861 -def test_suite():
    862      suite = unittest.TestSuite()
    863      suite.addTests([unittest.makeSuite(ETreeISOSchematronTestCase)])
    864      suite.addTests(doctest.DocTestSuite(isoschematron))
    865      suite.addTests(
    866          [make_doctest('../../../doc/validation.txt')])
    867      return suite
    868
    869  if __name__ == '__main__':
    870      print('to test use test.py %s' % __file__)
    871