d161ce46ea405bb76453a07e0626b251ebc9a9e5
[platform/upstream/python-lxml.git] / src / lxml / 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                         parser_ctxt = relaxng.xmlRelaxNGNewParserCtxt(_cstr(filename))
68             elif (_getFilenameForFile(file) or '')[-4:].lower() == '.rnc':
69                 _require_rnc2rng()
70                 rng_data_utf8 = _utf8(_rnc2rng.dumps(_rnc2rng.load(file)))
71                 doc = _parseMemoryDocument(
72                     rng_data_utf8, parser=None, url=_getFilenameForFile(file))
73                 parser_ctxt = relaxng.xmlRelaxNGNewDocParserCtxt(doc._c_doc)
74             else:
75                 doc = _parseDocument(file, parser=None, base_url=None)
76                 parser_ctxt = relaxng.xmlRelaxNGNewDocParserCtxt(doc._c_doc)
77         else:
78             raise RelaxNGParseError, u"No tree or file given"
79
80         if parser_ctxt is NULL:
81             if fake_c_doc is not NULL:
82                 _destroyFakeDoc(doc._c_doc, fake_c_doc)
83             raise RelaxNGParseError(
84                 self._error_log._buildExceptionMessage(
85                     u"Document is not parsable as Relax NG"),
86                 self._error_log)
87
88         relaxng.xmlRelaxNGSetParserStructuredErrors(
89             parser_ctxt, _receiveError, <void*>self._error_log)
90         _connectGenericErrorLog(self._error_log, xmlerror.XML_FROM_RELAXNGP)
91         self._c_schema = relaxng.xmlRelaxNGParse(parser_ctxt)
92         _connectGenericErrorLog(None)
93
94         relaxng.xmlRelaxNGFreeParserCtxt(parser_ctxt)
95         if self._c_schema is NULL:
96             if fake_c_doc is not NULL:
97                 _destroyFakeDoc(doc._c_doc, fake_c_doc)
98             raise RelaxNGParseError(
99                 self._error_log._buildExceptionMessage(
100                     u"Document is not valid Relax NG"),
101                 self._error_log)
102         if fake_c_doc is not NULL:
103             _destroyFakeDoc(doc._c_doc, fake_c_doc)
104
105     def __dealloc__(self):
106         relaxng.xmlRelaxNGFree(self._c_schema)
107
108     def __call__(self, etree):
109         u"""__call__(self, etree)
110
111         Validate doc using Relax NG.
112
113         Returns true if document is valid, false if not."""
114         cdef _Document doc
115         cdef _Element root_node
116         cdef xmlDoc* c_doc
117         cdef relaxng.xmlRelaxNGValidCtxt* valid_ctxt
118         cdef int ret
119
120         assert self._c_schema is not NULL, "RelaxNG instance not initialised"
121         doc = _documentOrRaise(etree)
122         root_node = _rootNodeOrRaise(etree)
123
124         valid_ctxt = relaxng.xmlRelaxNGNewValidCtxt(self._c_schema)
125         if valid_ctxt is NULL:
126             raise MemoryError()
127
128         try:
129             self._error_log.clear()
130             relaxng.xmlRelaxNGSetValidStructuredErrors(
131                 valid_ctxt, _receiveError, <void*>self._error_log)
132             _connectGenericErrorLog(self._error_log, xmlerror.XML_FROM_RELAXNGV)
133             c_doc = _fakeRootDoc(doc._c_doc, root_node._c_node)
134             with nogil:
135                 ret = relaxng.xmlRelaxNGValidateDoc(valid_ctxt, c_doc)
136             _destroyFakeDoc(doc._c_doc, c_doc)
137         finally:
138             _connectGenericErrorLog(None)
139             relaxng.xmlRelaxNGFreeValidCtxt(valid_ctxt)
140
141         if ret == -1:
142             raise RelaxNGValidateError(
143                 u"Internal error in Relax NG validation",
144                 self._error_log)
145         if ret == 0:
146             return True
147         else:
148             return False
149
150     @classmethod
151     def from_rnc_string(cls, src, base_url=None):
152         """Parse a RelaxNG schema in compact syntax from a text string
153
154         Requires the rnc2rng package to be installed.
155
156         Passing the source URL or file path of the source as 'base_url'
157         will enable resolving resource references relative to the source.
158         """
159         _require_rnc2rng()
160         rng_str = utf8(_rnc2rng.dumps(_rnc2rng.loads(src)))
161         return cls(_parseMemoryDocument(rng_str, parser=None, url=base_url))