/ lib / lxml / relaxng.pxi
relaxng.pxi
  1  # support for RelaxNG validation
  2  from lxml.includes cimport relaxng
  3  
  4  cdef object _rnc2rng
  5  try:
  6      import rnc2rng as _rnc2rng
  7  except ImportError:
  8      _rnc2rng = None
  9  
 10  
 11  cdef int _require_rnc2rng() except -1:
 12      if _rnc2rng is None:
 13          raise RelaxNGParseError(
 14              'compact syntax not supported (please install rnc2rng)')
 15      return 0
 16  
 17  
 18  cdef class RelaxNGError(LxmlError):
 19      """Base class for RelaxNG errors.
 20      """
 21  
 22  cdef class RelaxNGParseError(RelaxNGError):
 23      """Error while parsing an XML document as RelaxNG.
 24      """
 25  
 26  cdef class RelaxNGValidateError(RelaxNGError):
 27      """Error while validating an XML document with a RelaxNG schema.
 28      """
 29  
 30  
 31  ################################################################################
 32  # RelaxNG
 33  
 34  cdef class RelaxNG(_Validator):
 35      u"""RelaxNG(self, etree=None, file=None)
 36      Turn a document into a Relax NG validator.
 37  
 38      Either pass a schema as Element or ElementTree, or pass a file or
 39      filename through the ``file`` keyword argument.
 40      """
 41      cdef relaxng.xmlRelaxNG* _c_schema
 42      def __cinit__(self):
 43          self._c_schema = NULL
 44  
 45      def __init__(self, etree=None, *, file=None):
 46          cdef _Document doc
 47          cdef _Element root_node
 48          cdef xmlDoc* fake_c_doc = NULL
 49          cdef relaxng.xmlRelaxNGParserCtxt* parser_ctxt = NULL
 50          _Validator.__init__(self)
 51          if etree is not None:
 52              doc = _documentOrRaise(etree)
 53              root_node = _rootNodeOrRaise(etree)
 54              fake_c_doc = _fakeRootDoc(doc._c_doc, root_node._c_node)
 55              parser_ctxt = relaxng.xmlRelaxNGNewDocParserCtxt(fake_c_doc)
 56          elif file is not None:
 57              if _isString(file):
 58                  if file[-4:].lower() == '.rnc':
 59                      _require_rnc2rng()
 60                      rng_data_utf8 = _utf8(_rnc2rng.dumps(_rnc2rng.load(file)))
 61                      doc = _parseMemoryDocument(rng_data_utf8, parser=None, url=file)
 62                      parser_ctxt = relaxng.xmlRelaxNGNewDocParserCtxt(doc._c_doc)
 63                  else:
 64                      doc = None
 65                      filename = _encodeFilename(file)
 66                      with self._error_log:
 67                          orig_loader = _register_document_loader()
 68                          parser_ctxt = relaxng.xmlRelaxNGNewParserCtxt(_cstr(filename))
 69                          _reset_document_loader(orig_loader)
 70              elif (_getFilenameForFile(file) or '')[-4:].lower() == '.rnc':
 71                  _require_rnc2rng()
 72                  rng_data_utf8 = _utf8(_rnc2rng.dumps(_rnc2rng.load(file)))
 73                  doc = _parseMemoryDocument(
 74                      rng_data_utf8, parser=None, url=_getFilenameForFile(file))
 75                  parser_ctxt = relaxng.xmlRelaxNGNewDocParserCtxt(doc._c_doc)
 76              else:
 77                  doc = _parseDocument(file, parser=None, base_url=None)
 78                  parser_ctxt = relaxng.xmlRelaxNGNewDocParserCtxt(doc._c_doc)
 79          else:
 80              raise RelaxNGParseError, u"No tree or file given"
 81  
 82          if parser_ctxt is NULL:
 83              if fake_c_doc is not NULL:
 84                  _destroyFakeDoc(doc._c_doc, fake_c_doc)
 85              raise RelaxNGParseError(
 86                  self._error_log._buildExceptionMessage(
 87                      u"Document is not parsable as Relax NG"),
 88                  self._error_log)
 89  
 90          relaxng.xmlRelaxNGSetParserStructuredErrors(
 91              parser_ctxt, _receiveError, <void*>self._error_log)
 92          _connectGenericErrorLog(self._error_log, xmlerror.XML_FROM_RELAXNGP)
 93          self._c_schema = relaxng.xmlRelaxNGParse(parser_ctxt)
 94          _connectGenericErrorLog(None)
 95  
 96          relaxng.xmlRelaxNGFreeParserCtxt(parser_ctxt)
 97          if self._c_schema is NULL:
 98              if fake_c_doc is not NULL:
 99                  _destroyFakeDoc(doc._c_doc, fake_c_doc)
100              raise RelaxNGParseError(
101                  self._error_log._buildExceptionMessage(
102                      u"Document is not valid Relax NG"),
103                  self._error_log)
104          if fake_c_doc is not NULL:
105              _destroyFakeDoc(doc._c_doc, fake_c_doc)
106  
107      def __dealloc__(self):
108          relaxng.xmlRelaxNGFree(self._c_schema)
109  
110      def __call__(self, etree):
111          u"""__call__(self, etree)
112  
113          Validate doc using Relax NG.
114  
115          Returns true if document is valid, false if not."""
116          cdef _Document doc
117          cdef _Element root_node
118          cdef xmlDoc* c_doc
119          cdef relaxng.xmlRelaxNGValidCtxt* valid_ctxt
120          cdef int ret
121  
122          assert self._c_schema is not NULL, "RelaxNG instance not initialised"
123          doc = _documentOrRaise(etree)
124          root_node = _rootNodeOrRaise(etree)
125  
126          valid_ctxt = relaxng.xmlRelaxNGNewValidCtxt(self._c_schema)
127          if valid_ctxt is NULL:
128              raise MemoryError()
129  
130          try:
131              self._error_log.clear()
132              relaxng.xmlRelaxNGSetValidStructuredErrors(
133                  valid_ctxt, _receiveError, <void*>self._error_log)
134              _connectGenericErrorLog(self._error_log, xmlerror.XML_FROM_RELAXNGV)
135              c_doc = _fakeRootDoc(doc._c_doc, root_node._c_node)
136              with nogil:
137                  ret = relaxng.xmlRelaxNGValidateDoc(valid_ctxt, c_doc)
138              _destroyFakeDoc(doc._c_doc, c_doc)
139          finally:
140              _connectGenericErrorLog(None)
141              relaxng.xmlRelaxNGFreeValidCtxt(valid_ctxt)
142  
143          if ret == -1:
144              raise RelaxNGValidateError(
145                  u"Internal error in Relax NG validation",
146                  self._error_log)
147          if ret == 0:
148              return True
149          else:
150              return False
151  
152      @classmethod
153      def from_rnc_string(cls, src, base_url=None):
154          """Parse a RelaxNG schema in compact syntax from a text string
155  
156          Requires the rnc2rng package to be installed.
157  
158          Passing the source URL or file path of the source as 'base_url'
159          will enable resolving resource references relative to the source.
160          """
161          _require_rnc2rng()
162          rng_str = utf8(_rnc2rng.dumps(_rnc2rng.loads(src)))
163          return cls(_parseMemoryDocument(rng_str, parser=None, url=base_url))