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' %
    272              (schematron.validation_report))
    273
    274          schematron = isoschematron.Schematron(schema, store_report=False)
    275          self.assertTrue(schematron(tree_valid), schematron.error_log)
    276          valid = schematron(tree_invalid)
    277          self.assertTrue(not valid)
    278          self.assertTrue(schematron.validation_report is None,
    279              'validation reporting switched off, still: %s' %
    280              (schematron.validation_report))
    281
    282 -    def test_schematron_store_schematron(self):
    283          schema = self.parse('''\
    284  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    285    <sch:pattern id="number_of_entries">
    286      <sch:title>mandatory number_of_entries tests</sch:title>
    287      <sch:rule context="number_of_entries">
    288        <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>
    289      </sch:rule>
    290    </sch:pattern>
    291  </sch:schema>
    292  ''')
    293          schematron = isoschematron.Schematron(schema)
    294          self.assertTrue(schematron.validator_xslt is None)
    295
    296          schematron = isoschematron.Schematron(schema, store_schematron=True)
    297          self.assertTrue(isinstance(schematron.schematron, etree._ElementTree),
    298                       'expected schematron schema to be stored')
    299
    300 -    def test_schematron_store_xslt(self):
    301          schema = self.parse('''\
    302  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    303    <sch:pattern id="number_of_entries">
    304      <sch:title>mandatory number_of_entries tests</sch:title>
    305      <sch:rule context="number_of_entries">
    306        <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>
    307      </sch:rule>
    308    </sch:pattern>
    309  </sch:schema>
    310  ''')
    311          schematron = isoschematron.Schematron(schema)
    312          self.assertTrue(schematron.validator_xslt is None)
    313
    314          schematron = isoschematron.Schematron(schema, store_xslt=True)
    315          self.assertTrue(isinstance(schematron.validator_xslt, etree._ElementTree),
    316                       'expected validator xslt to be stored')
    317
    318 -    def test_schematron_abstract(self):
    319          schema = self.parse('''\
    320  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    321    <sch:title>iso schematron validation</sch:title>
    322    <sch:ns uri="http://www.w3.org/2001/XMLSchema-instance" prefix="xsi"/>
    323    <sch:ns uri="http://codespeak.net/lxml/objectify/pytype" prefix="py"/>
    324
    325    <!-- of course, these only really make sense when combined with a schema that
    326         ensures datatype xs:dateTime -->
    327
    328    <sch:pattern abstract="true" id="abstract.dateTime.tz_utc">
    329      <sch:rule context="$datetime">
    330        <sch:let name="tz" value="concat(substring-after(substring-after(./text(), 'T'), '+'), substring-after(substring-after(./text(), 'T'), '-'))"/>
    331        <sch:let name="lastchar" value="substring(./text(), string-length(./text()))"/>
    332        <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>
    333      </sch:rule>
    334    </sch:pattern>
    335
    336    <sch:pattern abstract="true" id="abstract.dateTime.tz_utc_nillable">
    337      <sch:rule context="$datetime">
    338        <sch:let name="tz" value="concat(substring-after(substring-after(./text(), 'T'), '+'), substring-after(substring-after(./text(), 'T'), '-'))"/>
    339        <sch:let name="lastchar" value="substring(./text(), string-length(./text()))"/>
    340        <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>
    341      </sch:rule>
    342    </sch:pattern>
    343
    344    <sch:pattern is-a="abstract.dateTime.tz_utc" id="datetime" >
    345      <sch:param name="datetime" value="datetime"/>
    346    </sch:pattern>
    347
    348    <sch:pattern is-a="abstract.dateTime.tz_utc_nillable" id="nillableDatetime">
    349      <sch:param name="datetime" value="nillableDatetime"/>
    350    </sch:pattern>
    351
    352  </sch:schema>
    353  ''')
    354          valid_trees = [
    355              self.parse('''\
    356  <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    357    <datetime>2009-12-10T15:21:00Z</datetime>
    358    <nillableDatetime xsi:nil="true"/>
    359  </root>
    360  '''),
    361              self.parse('''\
    362  <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    363    <datetime>2009-12-10T15:21:00Z</datetime>
    364    <nillableDatetime>2009-12-10T15:21:00Z</nillableDatetime>
    365  </root>
    366  '''),
    367              self.parse('''\
    368  <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    369    <datetime>2009-12-10T15:21:00+00:00</datetime>
    370    <nillableDatetime>2009-12-10T15:21:00-00:00</nillableDatetime>
    371  </root>
    372  '''),
    373              ]
    374
    375          schematron = isoschematron.Schematron(schema)
    376          for tree_valid in valid_trees:
    377              self.assertTrue(schematron(tree_valid), schematron.error_log)
    378
    379          tree_invalid = self.parse('''\
    380  <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    381    <datetime>2009-12-10T16:21:00+01:00</datetime>
    382    <nillableDatetime>2009-12-10T16:21:00+01:00</nillableDatetime>
    383  </root>
    384  ''')
    385          expected = 2
    386          valid = schematron(tree_invalid)
    387          self.assertTrue(not valid)
    388          self.assertEqual(
    389              len(schematron.error_log), expected,
    390              'expected %s errors: %s (%s errors)' %
    391              (expected, schematron.error_log, len(schematron.error_log)))
    392
    393          tree_invalid = self.parse('''\
    394  <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    395    <datetime xsi:nil="true"/>
    396    <nillableDatetime>2009-12-10T16:21:00Z</nillableDatetime>
    397  </root>
    398  ''')
    399          expected = 1
    400          valid = schematron(tree_invalid)
    401          self.assertTrue(not valid)
    402          self.assertEqual(
    403              len(schematron.error_log), expected,
    404              'expected %s errors: %s (%s errors)' %
    405              (expected, schematron.error_log, len(schematron.error_log)))
    406
    407 -    def test_schematron_phases(self):
    408          schema = self.parse('''\
    409  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    410    <sch:title>iso schematron validation</sch:title>
    411    <sch:ns uri="http://www.w3.org/2001/XMLSchema-instance" prefix="xsi"/>
    412    <sch:ns uri="http://codespeak.net/lxml/objectify/pytype" prefix="py"/>
    413
    414    <sch:phase id="mandatory">
    415      <sch:active pattern="number_of_entries"/>
    416    </sch:phase>
    417
    418    <sch:phase id="datetime_checks">
    419      <sch:active pattern="datetime"/>
    420      <sch:active pattern="nillableDatetime"/>
    421    </sch:phase>
    422
    423    <sch:phase id="full">
    424      <sch:active pattern="number_of_entries"/>
    425      <sch:active pattern="datetime"/>
    426      <sch:active pattern="nillableDatetime"/>
    427    </sch:phase>
    428
    429    <!-- of course, these only really make sense when combined with a schema that
    430         ensures datatype xs:dateTime -->
    431
    432    <sch:pattern abstract="true" id="abstract.dateTime.tz_utc">
    433      <sch:rule context="$datetime">
    434        <sch:let name="tz" value="concat(substring-after(substring-after(./text(), 'T'), '+'), substring-after(substring-after(./text(), 'T'), '-'))"/>
    435        <sch:let name="lastchar" value="substring(./text(), string-length(./text()))"/>
    436        <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>
    437      </sch:rule>
    438    </sch:pattern>
    439
    440    <sch:pattern abstract="true" id="abstract.dateTime.tz_utc_nillable">
    441      <sch:rule context="$datetime">
    442        <sch:let name="tz" value="concat(substring-after(substring-after(./text(), 'T'), '+'), substring-after(substring-after(./text(), 'T'), '-'))"/>
    443        <sch:let name="lastchar" value="substring(./text(), string-length(./text()))"/>
    444        <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>
    445      </sch:rule>
    446    </sch:pattern>
    447
    448    <sch:pattern id="number_of_entries">
    449      <sch:title>mandatory number_of_entries test</sch:title>
    450      <sch:rule context="number_of_entries">
    451        <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>
    452      </sch:rule>
    453    </sch:pattern>
    454
    455    <sch:pattern  id="datetime" is-a="abstract.dateTime.tz_utc">
    456      <sch:param name="datetime" value="datetime"/>
    457    </sch:pattern>
    458
    459    <sch:pattern  id="nillableDatetime" is-a="abstract.dateTime.tz_utc_nillable">
    460      <sch:param name="datetime" value="nillableDatetime"/>
    461    </sch:pattern>
    462
    463  </sch:schema>
    464  ''')
    465          tree_valid = self.parse('''\
    466  <message xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    467    <datetime>2009-12-10T15:21:00Z</datetime>
    468    <nillableDatetime xsi:nil="true"/>
    469    <number_of_entries>0</number_of_entries>
    470    <entries>
    471    </entries>
    472  </message>
    473  ''')
    474          tree_invalid = self.parse('''\
    475  <message>
    476    <datetime>2009-12-10T16:21:00+01:00</datetime>
    477    <nillableDatetime>2009-12-10T16:21:00+01:00</nillableDatetime>
    478    <number_of_entries>3</number_of_entries>
    479    <entries>
    480      <entry>Entry 1</entry>
    481      <entry>Entry 2</entry>
    482    </entries>
    483  </message>
    484  ''')
    485          # check everything (default phase #ALL)
    486          schematron = isoschematron.Schematron(schema)
    487          self.assertTrue(schematron(tree_valid), schematron.error_log)
    488          expected = 3
    489          valid = schematron(tree_invalid)
    490          self.assertTrue(not valid)
    491          self.assertEqual(
    492              len(schematron.error_log), expected,
    493              'expected %s errors: %s (%s errors)' %
    494              (expected, schematron.error_log, len(schematron.error_log)))
    495
    496          # check phase mandatory
    497          schematron = isoschematron.Schematron(
    498              schema, compile_params={'phase': 'mandatory'})
    499          self.assertTrue(schematron(tree_valid), schematron.error_log)
    500          expected = 1
    501          valid = schematron(tree_invalid)
    502          self.assertTrue(not valid)
    503          self.assertEqual(
    504              len(schematron.error_log), expected,
    505              'expected %s errors: %s (%s errors)' %
    506              (expected, schematron.error_log, len(schematron.error_log)))
    507
    508          # check phase datetime_checks
    509          schematron = isoschematron.Schematron(
    510              schema, compile_params={'phase': 'datetime_checks'})
    511          self.assertTrue(schematron(tree_valid), schematron.error_log)
    512          expected = 2
    513          valid = schematron(tree_invalid)
    514          self.assertTrue(not valid)
    515          self.assertEqual(
    516              len(schematron.error_log), expected,
    517              'expected %s errors: %s (%s errors)' %
    518              (expected, schematron.error_log, len(schematron.error_log)))
    519
    520          # check phase full
    521          schematron = isoschematron.Schematron(
    522              schema, compile_params={'phase': 'full'})
    523          self.assertTrue(schematron(tree_valid), schematron.error_log)
    524          expected = 3
    525          valid = schematron(tree_invalid)
    526          self.assertTrue(not valid)
    527          self.assertEqual(
    528              len(schematron.error_log), expected,
    529              'expected %s errors: %s (%s errors)' %
    530              (expected, schematron.error_log, len(schematron.error_log)))
    531
    532 -    def test_schematron_phases_kwarg(self):
    533          schema = self.parse('''\
    534  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    535    <sch:title>iso schematron validation</sch:title>
    536    <sch:ns uri="http://www.w3.org/2001/XMLSchema-instance" prefix="xsi"/>
    537    <sch:ns uri="http://codespeak.net/lxml/objectify/pytype" prefix="py"/>
    538
    539    <sch:phase id="mandatory">
    540      <sch:active pattern="number_of_entries"/>
    541    </sch:phase>
    542
    543    <sch:phase id="datetime_checks">
    544      <sch:active pattern="datetime"/>
    545      <sch:active pattern="nillableDatetime"/>
    546    </sch:phase>
    547
    548    <sch:phase id="full">
    549      <sch:active pattern="number_of_entries"/>
    550      <sch:active pattern="datetime"/>
    551      <sch:active pattern="nillableDatetime"/>
    552    </sch:phase>
    553
    554    <!-- of course, these only really make sense when combined with a schema that
    555         ensures datatype xs:dateTime -->
    556
    557    <sch:pattern abstract="true" id="abstract.dateTime.tz_utc">
    558      <sch:rule context="$datetime">
    559        <sch:let name="tz" value="concat(substring-after(substring-after(./text(), 'T'), '+'), substring-after(substring-after(./text(), 'T'), '-'))"/>
    560        <sch:let name="lastchar" value="substring(./text(), string-length(./text()))"/>
    561        <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>
    562      </sch:rule>
    563    </sch:pattern>
    564
    565    <sch:pattern abstract="true" id="abstract.dateTime.tz_utc_nillable">
    566      <sch:rule context="$datetime">
    567        <sch:let name="tz" value="concat(substring-after(substring-after(./text(), 'T'), '+'), substring-after(substring-after(./text(), 'T'), '-'))"/>
    568        <sch:let name="lastchar" value="substring(./text(), string-length(./text()))"/>
    569        <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>
    570      </sch:rule>
    571    </sch:pattern>
    572
    573    <sch:pattern id="number_of_entries">
    574      <sch:title>mandatory number_of_entries test</sch:title>
    575      <sch:rule context="number_of_entries">
    576        <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>
    577      </sch:rule>
    578    </sch:pattern>
    579
    580    <sch:pattern  id="datetime" is-a="abstract.dateTime.tz_utc">
    581      <sch:param name="datetime" value="datetime"/>
    582    </sch:pattern>
    583
    584    <sch:pattern  id="nillableDatetime" is-a="abstract.dateTime.tz_utc_nillable">
    585      <sch:param name="datetime" value="nillableDatetime"/>
    586    </sch:pattern>
    587
    588  </sch:schema>
    589  ''')
    590          tree_valid = self.parse('''\
    591  <message xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    592    <datetime>2009-12-10T15:21:00Z</datetime>
    593    <nillableDatetime xsi:nil="true"/>
    594    <number_of_entries>0</number_of_entries>
    595    <entries>
    596    </entries>
    597  </message>
    598  ''')
    599          tree_invalid = self.parse('''\
    600  <message>
    601    <datetime>2009-12-10T16:21:00+01:00</datetime>
    602    <nillableDatetime>2009-12-10T16:21:00+01:00</nillableDatetime>
    603    <number_of_entries>3</number_of_entries>
    604    <entries>
    605      <entry>Entry 1</entry>
    606      <entry>Entry 2</entry>
    607    </entries>
    608  </message>
    609  ''')
    610          # check everything (default phase #ALL)
    611          schematron = isoschematron.Schematron(schema)
    612          self.assertTrue(schematron(tree_valid), schematron.error_log)
    613          expected = 3
    614          valid = schematron(tree_invalid)
    615          self.assertTrue(not valid)
    616          self.assertEqual(
    617              len(schematron.error_log), expected,
    618              'expected %s errors: %s (%s errors)' %
    619              (expected, schematron.error_log, len(schematron.error_log)))
    620
    621          # check phase mandatory
    622          schematron = isoschematron.Schematron(schema, phase='mandatory')
    623          self.assertTrue(schematron(tree_valid), schematron.error_log)
    624          expected = 1
    625          valid = schematron(tree_invalid)
    626          self.assertTrue(not valid)
    627          self.assertEqual(
    628              len(schematron.error_log), expected,
    629              'expected %s errors: %s (%s errors)' %
    630              (expected, schematron.error_log, len(schematron.error_log)))
    631
    632          # check phase datetime_checks
    633          schematron = isoschematron.Schematron(schema, phase='datetime_checks')
    634          self.assertTrue(schematron(tree_valid), schematron.error_log)
    635          expected = 2
    636          valid = schematron(tree_invalid)
    637          self.assertTrue(not valid)
    638          self.assertEqual(
    639              len(schematron.error_log), expected,
    640              'expected %s errors: %s (%s errors)' %
    641              (expected, schematron.error_log, len(schematron.error_log)))
    642
    643          # check phase full
    644          schematron = isoschematron.Schematron(schema, phase='full')
    645          self.assertTrue(schematron(tree_valid), schematron.error_log)
    646          expected = 3
    647          valid = schematron(tree_invalid)
    648          self.assertTrue(not valid)
    649          self.assertEqual(
    650              len(schematron.error_log), expected, 'expected %s errors: %s (%s errors)' %
    651              (expected, schematron.error_log, len(schematron.error_log)))
    652
    653 -    def test_schematron_xmlschema_embedded(self):
    654          schema = self.parse('''\
    655  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    656      xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    657      <xs:element name="message">
    658          <xs:complexType>
    659              <xs:sequence>
    660                  <xs:element name="number_of_entries" type="xs:positiveInteger">
    661                      <xs:annotation>
    662                          <xs:appinfo>
    663                              <sch:pattern id="number_of_entries">
    664                                  <sch:title>mandatory number_of_entries tests</sch:title>
    665                                  <sch:rule context="number_of_entries">
    666                                      <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>
    667                                  </sch:rule>
    668                              </sch:pattern>
    669                          </xs:appinfo>
    670                      </xs:annotation>
    671                  </xs:element>
    672                  <xs:element name="entries">
    673                      <xs:complexType>
    674                          <xs:sequence>
    675                              <xs:element name="entry" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
    676                          </xs:sequence>
    677                      </xs:complexType>
    678                  </xs:element>
    679              </xs:sequence>
    680          </xs:complexType>
    681      </xs:element>
    682  </xs:schema>
    683  ''')
    684          tree_valid = self.parse('''\
    685  <message>
    686    <number_of_entries>2</number_of_entries>
    687    <entries>
    688      <entry>Entry 1</entry>
    689      <entry>Entry 2</entry>
    690    </entries>
    691  </message>
    692  ''')
    693          tree_invalid = self.parse('''\
    694  <message>
    695    <number_of_entries>1</number_of_entries>
    696    <entries>
    697      <entry>Entry 1</entry>
    698      <entry>Entry 2</entry>
    699    </entries>
    700  </message>
    701  ''')
    702          xmlschema = etree.XMLSchema(schema)
    703          schematron = isoschematron.Schematron(schema)
    704          # fwiw, this must also be XMLSchema-valid
    705          self.assertTrue(xmlschema(tree_valid), xmlschema.error_log)
    706          self.assertTrue(schematron(tree_valid))
    707          # still schema-valid
    708          self.assertTrue(xmlschema(tree_invalid), xmlschema.error_log)
    709          self.assertTrue(not schematron(tree_invalid))
    710
    711 -    def test_schematron_relaxng_embedded(self):
    712          schema = self.parse('''\
    713  <grammar xmlns="http://relaxng.org/ns/structure/1.0"
    714    xmlns:sch="http://purl.oclc.org/dsdl/schematron"
    715    datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
    716    <start>
    717      <ref name="message"/>
    718    </start>
    719    <define name="message">
    720      <element name="message">
    721        <element name="number_of_entries">
    722          <!-- RelaxNG can be mixed freely with stuff from other namespaces -->
    723          <sch:pattern id="number_of_entries">
    724            <sch:title>mandatory number_of_entries tests</sch:title>
    725            <sch:rule context="number_of_entries">
    726              <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>
    727            </sch:rule>
    728          </sch:pattern>
    729          <data type="positiveInteger"/>
    730        </element>
    731        <element name="entries">
    732          <zeroOrMore>
    733            <element name="entry"><data type="string"/></element>
    734          </zeroOrMore>
    735        </element>
    736      </element>
    737    </define>
    738  </grammar>
    739  ''')
    740          tree_valid = self.parse('''\
    741  <message>
    742    <number_of_entries>2</number_of_entries>
    743    <entries>
    744      <entry>Entry 1</entry>
    745      <entry>Entry 2</entry>
    746    </entries>
    747  </message>
    748  ''')
    749          tree_invalid = self.parse('''\
    750  <message>
    751    <number_of_entries>1</number_of_entries>
    752    <entries>
    753      <entry>Entry 1</entry>
    754      <entry>Entry 2</entry>
    755    </entries>
    756  </message>
    757  ''')
    758          relaxng = etree.RelaxNG(schema)
    759          schematron = isoschematron.Schematron(schema)
    760          # fwiw, this must also be RelaxNG-valid
    761          self.assertTrue(relaxng(tree_valid), relaxng.error_log)
    762          self.assertTrue(schematron(tree_valid))
    763          # still schema-valid
    764          self.assertTrue(relaxng(tree_invalid), relaxng.error_log)
    765          self.assertTrue(not schematron(tree_invalid))
    766
    767 -    def test_schematron_invalid_args(self):
    768          schema = self.parse('''\
    769  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    770    <sch:pattern id="number_of_entries">
    771      <sch:title>mandatory number_of_entries tests</sch:title>
    772      <sch:rule context="number_of_entries">
    773        <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>
    774      </sch:rule>
    775    </sch:pattern>
    776  </sch:schema>
    777  ''')
    778          # handing phase as keyword arg will *not* raise the type error
    779          self.assertRaises(TypeError, isoschematron.Schematron, schema,
    780                            compile_params={'phase': None})
    781
    782 -    def test_schematron_customization(self):
    783          class MySchematron(isoschematron.Schematron):
    784              def _extract(self, root):
    785                  schematron = (root.xpath(
    786                      '//sch:schema',
    787                      namespaces={'sch': "http://purl.oclc.org/dsdl/schematron"})
    788                      or [None])[0]
    789                  return schematron
    790
    791              def _include(self, schematron, **kwargs):
    792                  raise RuntimeError('inclusion unsupported')
    793
    794              def _expand(self, schematron, **kwargs):
    795                  raise RuntimeError('expansion unsupported')
    796
    797              def _validation_errors(self, validationReport):
    798                  valid = etree.XPath(
    799                      'count(//svrl:successful-report[@flag="critical"])=1',
    800                      namespaces={'svrl': isoschematron.SVRL_NS})(
    801                      validationReport)
    802                  if valid:
    803                      return []
    804                  error = etree.Element('Error')
    805                  error.text = 'missing critical condition report'
    806                  return [error]
    807
    808          tree_valid = self.parse('<AAA><BBB/><CCC/></AAA>')
    809          tree_invalid = self.parse('<AAA><BBB/><CCC/><DDD/></AAA>')
    810          schema = self.parse('''\
    811  <schema xmlns="http://www.example.org/yet/another/schema/dialect">
    812    <schema xmlns="http://purl.oclc.org/dsdl/schematron" >
    813      <pattern id="OpenModel">
    814        <title>Open Model</title>
    815        <rule context="AAA">
    816          <report test="BBB" flag="info">BBB element must be present</report>
    817          <report test="CCC" flag="info">CCC element must be present</report>
    818        </rule>
    819      </pattern>
    820      <pattern id="ClosedModel">
    821        <title>Closed model"</title>
    822        <rule context="AAA">
    823          <report test="BBB" flag="info">BBB element must be present</report>
    824          <report test="CCC" flag="info">CCC element must be present</report>
    825          <report test="count(BBB|CCC) = count(*)" flag="critical">Only BBB and CCC children must be present</report>
    826        </rule>
    827      </pattern>
    828    </schema>
    829  </schema>
    830  ''')
    831          # check if overridden _include is run
    832          self.assertRaises(RuntimeError, MySchematron, schema, store_report=True)
    833          # check if overridden _expand is run
    834          self.assertRaises(RuntimeError, MySchematron, schema, store_report=True,
    835                            include=False)
    836
    837          schema = MySchematron(schema, store_report=True, include=False,
    838                                expand=False)
    839          self.assertTrue(schema.validate(tree_valid))
    840          self.assertTrue(not schema.validate(tree_invalid))
    841
    842      #TODO: test xslt parameters for inclusion, expand & compile steps (?)
    843
    844 -    def test_schematron_fail_on_report(self):
    845          tree_valid = self.parse('<AAA><BBB/><CCC/></AAA>')
    846          tree_invalid = self.parse('<AAA><BBB/><CCC/><DDD/></AAA>')
    847          schema = self.parse('''\
    848  <schema xmlns="http://purl.oclc.org/dsdl/schematron" >
    849      <pattern id="OpenModel">
    850          <title>Simple Report</title>
    851          <rule context="AAA">
    852              <report test="DDD"> DDD element must not be present</report>
    853          </rule>
    854      </pattern>
    855  </schema>
    856  ''')
    857          schema_report = isoschematron.Schematron(
    858              schema, error_finder=isoschematron.Schematron.ASSERTS_AND_REPORTS)
    859          schema_no_report = isoschematron.Schematron(schema)
    860          self.assertTrue(schema_report.validate(tree_valid))
    861          self.assertTrue(not schema_report.validate(tree_invalid))
    862          self.assertTrue(schema_no_report.validate(tree_valid))
    863          self.assertTrue(schema_no_report.validate(tree_invalid))
    864
    865
    866 -def test_suite():
    867      suite = unittest.TestSuite()
    868      suite.addTests([unittest.makeSuite(ETreeISOSchematronTestCase)])
    869      suite.addTests(doctest.DocTestSuite(isoschematron))
    870      suite.addTests(
    871          [make_doctest('../../../doc/validation.txt')])
    872      return suite
    873
    874  if __name__ == '__main__':
    875      print('to test use test.py %s' % __file__)
    876