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