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 -class ETreeISOSchematronTestCase(HelperTestCase):
     18 -    def test_schematron(self):
     19          tree_valid = self.parse('<AAA><BBB/><CCC/></AAA>')
     20          tree_invalid = self.parse('<AAA><BBB/><CCC/><DDD/></AAA>')
     21          schema = self.parse('''\
     22  <schema xmlns="http://purl.oclc.org/dsdl/schematron" >
     23      <pattern id="OpenModel">
     24          <title>Open Model</title>
     25          <rule context="AAA">
     26              <assert test="BBB"> BBB element is not present</assert>
     27              <assert test="CCC"> CCC element is not present</assert>
     28          </rule>
     29      </pattern>
     30      <pattern id="ClosedModel">
     31          <title>Closed model"</title>
     32          <rule context="AAA">
     33              <assert test="BBB"> BBB element is not present</assert>
     34              <assert test="CCC"> CCC element is not present</assert>
     35              <assert test="count(BBB|CCC) = count (*)">There is an extra element</assert>
     36          </rule>
     37      </pattern>
     38  </schema>
     39  ''')
     40          schema = isoschematron.Schematron(schema)
     41          self.assert_(schema.validate(tree_valid))
     42          self.assert_(not schema.validate(tree_invalid))
     43
     44 -    def test_schematron_elementtree_error(self):
     45          self.assertRaises(ValueError, isoschematron.Schematron, etree.ElementTree())
     46
     47      # an empty pattern is valid in iso schematron
     48 -    def test_schematron_empty_pattern(self):
     49          schema = self.parse('''\
     50  <schema xmlns="http://purl.oclc.org/dsdl/schematron" >
     51      <pattern id="OpenModel">
     52          <title>Open model</title>
     53      </pattern>
     54  </schema>
     55  ''')
     56          schema = isoschematron.Schematron(schema)
     57          self.assert_(schema)
     58
     59 -    def test_schematron_invalid_schema_empty(self):
     60          schema = self.parse('''\
     61  <schema xmlns="http://purl.oclc.org/dsdl/schematron" />
     62  ''')
     63          self.assertRaises(etree.SchematronParseError,
     64                            isoschematron.Schematron, schema)
     65
     66 -    def test_schematron_invalid_schema_namespace(self):
     67          schema = self.parse('''\
     68  <schema xmlns="mynamespace" />
     69  ''')
     70          self.assertRaises(etree.SchematronParseError,
     71                            isoschematron.Schematron, schema)
     72
     73 -    def test_schematron_from_tree(self):
     74          schema = self.parse('''\
     75  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
     76    <sch:pattern id="number_of_entries">
     77      <sch:title>mandatory number_of_entries tests</sch:title>
     78      <sch:rule context="number_of_entries">
     79        <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>
     80      </sch:rule>
     81    </sch:pattern>
     82  </sch:schema>
     83  ''')
     84          schematron = isoschematron.Schematron(schema)
     85          self.assert_(isinstance(schematron, isoschematron.Schematron))
     86
     87 -    def test_schematron_from_element(self):
     88          schema = self.parse('''\
     89  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
     90    <sch:pattern id="number_of_entries">
     91      <sch:title>mandatory number_of_entries tests</sch:title>
     92      <sch:rule context="number_of_entries">
     93        <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>
     94      </sch:rule>
     95    </sch:pattern>
     96  </sch:schema>
     97  ''')
     98          schematron = isoschematron.Schematron(schema.getroot())
     99          self.assert_(isinstance(schematron, isoschematron.Schematron))
    100
    101 -    def test_schematron_from_file(self):
    102          schematron = isoschematron.Schematron(file=fileInTestDir('test.sch'))
    103          self.assert_(isinstance(schematron, isoschematron.Schematron))
    104
    105 -    def test_schematron_call(self):
    106          schema = self.parse('''\
    107  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    108    <sch:pattern id="number_of_entries">
    109      <sch:title>mandatory number_of_entries tests</sch:title>
    110      <sch:rule context="number_of_entries">
    111        <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>
    112      </sch:rule>
    113    </sch:pattern>
    114  </sch:schema>
    115  ''')
    116          tree_valid = self.parse('''\
    117  <message>
    118    <number_of_entries>0</number_of_entries>
    119    <entries>
    120    </entries>
    121  </message>
    122  ''')
    123          tree_invalid = self.parse('''\
    124  <message>
    125    <number_of_entries>3</number_of_entries>
    126    <entries>
    127      <entry>Entry 1</entry>
    128      <entry>Entry 2</entry>
    129    </entries>
    130  </message>
    131  ''')
    132          schematron = isoschematron.Schematron(schema)
    133          self.assert_(schematron(tree_valid), schematron.error_log)
    134          valid = schematron(tree_invalid)
    135          self.assert_(not valid)
    136
    137 -    def test_schematron_validate(self):
    138          schema = self.parse('''\
    139  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    140    <sch:pattern id="number_of_entries">
    141      <sch:title>mandatory number_of_entries tests</sch:title>
    142      <sch:rule context="number_of_entries">
    143        <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>
    144      </sch:rule>
    145    </sch:pattern>
    146  </sch:schema>
    147  ''')
    148          tree_valid = self.parse('''\
    149  <message>
    150    <number_of_entries>0</number_of_entries>
    151    <entries>
    152    </entries>
    153  </message>
    154  ''')
    155          tree_invalid = self.parse('''\
    156  <message>
    157    <number_of_entries>3</number_of_entries>
    158    <entries>
    159      <entry>Entry 1</entry>
    160      <entry>Entry 2</entry>
    161    </entries>
    162  </message>
    163  ''')
    164          schematron = isoschematron.Schematron(schema)
    165          self.assert_(schematron.validate(tree_valid), schematron.error_log)
    166          valid = schematron.validate(tree_invalid)
    167          self.assert_(not valid)
    168
    169 -    def test_schematron_assertValid(self):
    170          schema = self.parse('''\
    171  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    172    <sch:pattern id="number_of_entries">
    173      <sch:title>mandatory number_of_entries tests</sch:title>
    174      <sch:rule context="number_of_entries">
    175        <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>
    176      </sch:rule>
    177    </sch:pattern>
    178  </sch:schema>
    179  ''')
    180          tree_valid = self.parse('''\
    181  <message>
    182    <number_of_entries>0</number_of_entries>
    183    <entries>
    184    </entries>
    185  </message>
    186  ''')
    187          tree_invalid = self.parse('''\
    188  <message>
    189    <number_of_entries>3</number_of_entries>
    190    <entries>
    191      <entry>Entry 1</entry>
    192      <entry>Entry 2</entry>
    193    </entries>
    194  </message>
    195  ''')
    196          schematron = isoschematron.Schematron(schema)
    197          self.assert_(schematron(tree_valid), schematron.error_log)
    198          self.assertRaises(etree.DocumentInvalid, schematron.assertValid,
    199                            tree_invalid)
    200
    201 -    def test_schematron_error_log(self):
    202          schema = self.parse('''\
    203  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    204    <sch:pattern id="number_of_entries">
    205      <sch:title>mandatory number_of_entries tests</sch:title>
    206      <sch:rule context="number_of_entries">
    207        <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>
    208      </sch:rule>
    209    </sch:pattern>
    210  </sch:schema>
    211  ''')
    212          tree_valid = self.parse('''\
    213  <message>
    214    <number_of_entries>0</number_of_entries>
    215    <entries>
    216    </entries>
    217  </message>
    218  ''')
    219          tree_invalid = self.parse('''\
    220  <message>
    221    <number_of_entries>3</number_of_entries>
    222    <entries>
    223      <entry>Entry 1</entry>
    224      <entry>Entry 2</entry>
    225    </entries>
    226  </message>
    227  ''')
    228          schematron = isoschematron.Schematron(schema)
    229          self.assert_(schematron(tree_valid), schematron.error_log)
    230          valid = schematron(tree_invalid)
    231          self.assert_(not valid)
    232          self.assertEquals(len(schematron.error_log), 1,
    233                            'expected single error: %s (%s errors)' %
    234                            (schematron.error_log, len(schematron.error_log)))
    235
    236 -    def test_schematron_result_report(self):
    237          schema = self.parse('''\
    238  <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
    239    <sch:pattern id="number_of_entries">
    240      <sch:title>mandatory number_of_entries tests</sch:title>
    241      <sch:rule context="number_of_entries">
    242        <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>
    243      </sch:rule>
    244    </sch:pattern>
    245  </sch:schema>
    246  ''')
    247          tree_valid = self.parse('''\
    248  <message>
    249    <number_of_entries>0</number_of_entries>
    250    <entries>
    251    </entries>
    252  </message>
    253  ''')
    254          tree_invalid = self.parse('''\
    255  <message>
    256    <number_of_entries>3</number_of_entries>
    257    <entries>
    258      <entry>Entry 1</entry>
    259      <entry>Entry 2</entry>
    260    </entries>
    261  </message>
    262  ''')
    263          schematron = isoschematron.Schematron(schema, store_report=True)
    264          self.assert_(schematron(tree_valid), schematron.error_log)
    265          valid = schematron(tree_invalid)
    266          self.assert_(not valid)
    267          self.assert_(
    268              isinstance(schematron.validation_report, etree._ElementTree),
    269              'expected a validation report result tree, got: %s' %
    270              (schematron.validation_report))
    271
    272          schematron = isoschematron.Schematron(schema, store_report=False)
    273          self.assert_(schematron(tree_valid), schematron.error_log)
    274          valid = schematron(tree_invalid)
    275          self.assert_(not valid)
    276          self.assert_(schematron.validation_report is None,
    277              'validation reporting switched off, still: %s' %
    278              (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.assert_(schematron.validator_xslt is None)
    293
    294          schematron = isoschematron.Schematron(schema, store_schematron=True)
    295          self.assert_(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.assert_(schematron.validator_xslt is None)
    311
    312          schematron = isoschematron.Schematron(schema, store_xslt=True)
    313          self.assert_(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.assert_(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.assert_(not valid)
    386          self.assertEquals(
    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.assert_(not valid)
    400          self.assertEquals(
    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.assert_(schematron(tree_valid), schematron.error_log)
    486          expected = 3
    487          valid = schematron(tree_invalid)
    488          self.assert_(not valid)
    489          self.assertEquals(
    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.assert_(schematron(tree_valid), schematron.error_log)
    498          expected = 1
    499          valid = schematron(tree_invalid)
    500          self.assert_(not valid)
    501          self.assertEquals(
    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.assert_(schematron(tree_valid), schematron.error_log)
    510          expected = 2
    511          valid = schematron(tree_invalid)
    512          self.assert_(not valid)
    513          self.assertEquals(
    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.assert_(schematron(tree_valid), schematron.error_log)
    522          expected = 3
    523          valid = schematron(tree_invalid)
    524          self.assert_(not valid)
    525          self.assertEquals(
    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.assert_(schematron(tree_valid), schematron.error_log)
    611          expected = 3
    612          valid = schematron(tree_invalid)
    613          self.assert_(not valid)
    614          self.assertEquals(
    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.assert_(schematron(tree_valid), schematron.error_log)
    622          expected = 1
    623          valid = schematron(tree_invalid)
    624          self.assert_(not valid)
    625          self.assertEquals(
    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.assert_(schematron(tree_valid), schematron.error_log)
    633          expected = 2
    634          valid = schematron(tree_invalid)
    635          self.assert_(not valid)
    636          self.assertEquals(
    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.assert_(schematron(tree_valid), schematron.error_log)
    644          expected = 3
    645          valid = schematron(tree_invalid)
    646          self.assert_(not valid)
    647          self.assertEquals(
    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.assert_(xmlschema(tree_valid), xmlschema.error_log)
    704          self.assert_(schematron(tree_valid))
    705          # still schema-valid
    706          self.assert_(xmlschema(tree_invalid), xmlschema.error_log)
    707          self.assert_(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.assert_(relaxng(tree_valid), relaxng.error_log)
    760          self.assert_(schematron(tree_valid))
    761          # still schema-valid
    762          self.assert_(relaxng(tree_invalid), relaxng.error_log)
    763          self.assert_(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.assert_(schema.validate(tree_valid))
    838          self.assert_(not schema.validate(tree_invalid))
    839
    840      #TODO: test xslt parameters for inclusion, expand & compile steps (?)
    841
    842
    843 -def test_suite():
    844      suite = unittest.TestSuite()
    845      suite.addTests([unittest.makeSuite(ETreeISOSchematronTestCase)])
    846      suite.addTests(
    847          [make_doctest('../../../doc/validation.txt')])
    848      return suite
    849
    850  if __name__ == '__main__':
    851      print('to test use test.py %s' % __file__)
    852