docs: def schematron(self, tag=None, schemas=None, schemafn=None, ext='.sch'):
          """validate the XML using Schematron; the schema might be a schematron file,
              or it might be a RelaxNG schema that contains embedded Schematron.
          """
          from . import PATH
          from .xslt import XSLT
  
          schematron_path = os.path.join(PATH, 'schematron', 'trunk', 'schematron', 'code')
  
          # select the schema filename
          tag = tag or self.root.tag
          schemas = schemas or self.schemas
          schemafn = (
              schemafn
              or Schema.filename(tag, schemas, ext=ext)
              or Schema.filename(tag, schemas, ext='.rng')
              or Schema.filename(tag, schemas, ext='.rnc')
          )
  
          assert schemafn is not None and os.path.exists(schemafn)
  
          # ensure that an up-to-date Schematron schema file is available.
          schfn = File(fn=schemafn).clean_filename(ext='.sch')
          fbase, fext = os.path.splitext(schfn)
          if not (os.path.exists(schfn)) or (
              os.path.exists(fbase + '.rnc')
              and File(fn=schfn).last_modified < File(fn=fbase + '.rnc').last_modified
          ):
              # create the Schematron file from the .rnc
              schfn = Schema(fn=fbase + '.rnc').schematron()
          elif not (os.path.exists(schfn)) or (
              os.path.exists(fbase + '.rng')
              and File(fn=schfn).last_modified < File(fn=fbase + '.rng').last_modified
          ):
              # create the Schematron file from the .rng
              schfn = Schema(fn=fbase + '.rng').schematron()
  
          assert schfn is not None and os.path.exists(schfn)
  
          # ensure that an up-to-date Schematron XSLT file is available
          sch_xslt_fn = schfn + '.xslt'
          if (
              not os.path.exists(sch_xslt_fn)
              or File(fn=sch_xslt_fn).last_modified < File(fn=schfn).last_modified
          ):
              # The Schematron XSLT doesn't exist or is out-of-date, so we need to update it
              sch = XML(fn=schfn)
              sch.fn = sch_xslt_fn
  
              # We're using the XSLT 1.0 version of Schematron.
              # even though we use XSLT 2.0+ XPath functions -- the converter doesn't actually read the patterns.
              # (tried the XSLT 2.0 version with Saxon9HE, but Schematron assumes XSLT 1.0 compatibility,
              # which Saxon9HE doesn't support.)
              for xslfb in [
                  'iso_dsdl_include.xsl',
                  'iso_abstract_expand.xsl',
                  'iso_svrl_for_xslt1.xsl',
              ]:
                  xslt = XSLT(fn=os.path.join(schematron_path, xslfb))
                  sch.root = xslt.saxon6(sch.root).getroot()
                  sch.write()
  
              # change the XSLT version from "1.0" to "2.0". (Later we might support 3.0)
              sch.root.set('version', '2.0')
              sch.write()
  
          assert sch_xslt_fn is not None and os.path.exists(sch_xslt_fn)
  
          # validate the XML against the Schematron XSLT using Saxon 9 (XSLT 3.0 / XPath 3.1)
          sch_xslt = XSLT(fn=sch_xslt_fn)
          report = XML(root=sch_xslt.saxon9(self.root).getroot())
  
          # return the errors in the report_xml as a list of errors, with line numbers
          errors = []
          for failure in report.xpath(
              report.root,
              "//svrl:failed-assert",
              namespaces={'svrl': "http://purl.oclc.org/dsdl/svrl"},
          ):
              for e in self.xpath(self.root, failure.get('location')):
                  errors.append(
                      "%s:%d <%s %s>: error: %s"
                      % (
                          self.fn,
                          e.sourceline or -1,
                          self.tag_name(e),
                          " ".join(['%s="%s"' % (k, v) for k, v in e.attrib.items()]),
                          etree.tounicode(failure, method='text').strip(),
                      )
                  )
          return errors