e7b49600ca6b9f45c084f35275364def64ba1972
[platform/upstream/python-lxml.git] / src / lxml / xslt.pxi
1
2 # XSLT
3 from lxml.includes cimport xslt
4
5
6 cdef class XSLTError(LxmlError):
7     """Base class of all XSLT errors.
8     """
9
10 cdef class XSLTParseError(XSLTError):
11     """Error parsing a stylesheet document.
12     """
13
14 cdef class XSLTApplyError(XSLTError):
15     """Error running an XSL transformation.
16     """
17
18 class XSLTSaveError(XSLTError, SerialisationError):
19     """Error serialising an XSLT result.
20     """
21
22 cdef class XSLTExtensionError(XSLTError):
23     """Error registering an XSLT extension.
24     """
25
26
27 # version information
28 LIBXSLT_COMPILED_VERSION = __unpackIntVersion(xslt.LIBXSLT_VERSION)
29 LIBXSLT_VERSION = __unpackIntVersion(xslt.xsltLibxsltVersion)
30
31
32 ################################################################################
33 # Where do we store what?
34 #
35 # xsltStylesheet->doc->_private
36 #    == _XSLTResolverContext for XSL stylesheet
37 #
38 # xsltTransformContext->_private
39 #    == _XSLTResolverContext for transformed document
40 #
41 ################################################################################
42
43
44 ################################################################################
45 # XSLT document loaders
46
47 @cython.final
48 @cython.internal
49 cdef class _XSLTResolverContext(_ResolverContext):
50     cdef xmlDoc* _c_style_doc
51     cdef _BaseParser _parser
52
53     cdef _XSLTResolverContext _copy(self):
54         cdef _XSLTResolverContext context
55         context = _XSLTResolverContext()
56         _initXSLTResolverContext(context, self._parser)
57         context._c_style_doc = self._c_style_doc
58         return context
59
60 cdef _initXSLTResolverContext(_XSLTResolverContext context,
61                               _BaseParser parser):
62     _initResolverContext(context, parser.resolvers)
63     context._parser = parser
64     context._c_style_doc = NULL
65
66 cdef xmlDoc* _xslt_resolve_from_python(const_xmlChar* c_uri, void* c_context,
67                                        int parse_options, int* error) with gil:
68     # call the Python document loaders
69     cdef _XSLTResolverContext context
70     cdef _ResolverRegistry resolvers
71     cdef _InputDocument doc_ref
72     cdef xmlDoc* c_doc
73     cdef xmlDoc* c_return_doc = NULL
74
75     error[0] = 0
76     context = <_XSLTResolverContext>c_context
77
78     # shortcut if we resolve the stylesheet itself
79     c_doc = context._c_style_doc
80     try:
81         if c_doc is not NULL and c_doc.URL is not NULL:
82             if tree.xmlStrcmp(c_uri, c_doc.URL) == 0:
83                 c_return_doc = _copyDoc(c_doc, 1)
84                 return c_return_doc  # 'goto', see 'finally' below
85
86         # delegate to the Python resolvers
87         resolvers = context._resolvers
88         if tree.xmlStrncmp(<unsigned char*>'string://__STRING__XSLT__/', c_uri, 26) == 0:
89             c_uri += 26
90         uri = _decodeFilename(c_uri)
91         doc_ref = resolvers.resolve(uri, None, context)
92
93         if doc_ref is not None:
94             if doc_ref._type == PARSER_DATA_STRING:
95                 c_return_doc = _parseDoc(
96                     doc_ref._data_bytes, doc_ref._filename, context._parser)
97             elif doc_ref._type == PARSER_DATA_FILENAME:
98                 c_return_doc = _parseDocFromFile(
99                     doc_ref._filename, context._parser)
100             elif doc_ref._type == PARSER_DATA_FILE:
101                 c_return_doc = _parseDocFromFilelike(
102                     doc_ref._file, doc_ref._filename, context._parser)
103             elif doc_ref._type == PARSER_DATA_EMPTY:
104                 c_return_doc = _newXMLDoc()
105             if c_return_doc is not NULL and c_return_doc.URL is NULL:
106                 c_return_doc.URL = tree.xmlStrdup(c_uri)
107     except:
108         error[0] = 1
109         context._store_raised()
110     finally:
111         return c_return_doc  # and swallow any further exceptions
112
113
114 cdef void _xslt_store_resolver_exception(const_xmlChar* c_uri, void* context,
115                                          xslt.xsltLoadType c_type) with gil:
116     try:
117         message = f"Cannot resolve URI {_decodeFilename(c_uri)}"
118         if c_type == xslt.XSLT_LOAD_DOCUMENT:
119             exception = XSLTApplyError(message)
120         else:
121             exception = XSLTParseError(message)
122         (<_XSLTResolverContext>context)._store_exception(exception)
123     except BaseException as e:
124         (<_XSLTResolverContext>context)._store_exception(e)
125     finally:
126         return  # and swallow any further exceptions
127
128
129 cdef xmlDoc* _xslt_doc_loader(const_xmlChar* c_uri, tree.xmlDict* c_dict,
130                               int parse_options, void* c_ctxt,
131                               xslt.xsltLoadType c_type) nogil:
132     # nogil => no Python objects here, may be called without thread context !
133     cdef xmlDoc* c_doc
134     cdef xmlDoc* result
135     cdef void* c_pcontext
136     cdef int error = 0
137     # find resolver contexts of stylesheet and transformed doc
138     if c_type == xslt.XSLT_LOAD_DOCUMENT:
139         # transformation time
140         c_pcontext = (<xslt.xsltTransformContext*>c_ctxt)._private
141     elif c_type == xslt.XSLT_LOAD_STYLESHEET:
142         # include/import resolution while parsing
143         c_pcontext = (<xslt.xsltStylesheet*>c_ctxt).doc._private
144     else:
145         c_pcontext = NULL
146
147     if c_pcontext is NULL:
148         # can't call Python without context, fall back to default loader
149         return XSLT_DOC_DEFAULT_LOADER(
150             c_uri, c_dict, parse_options, c_ctxt, c_type)
151
152     c_doc = _xslt_resolve_from_python(c_uri, c_pcontext, parse_options, &error)
153     if c_doc is NULL and not error:
154         c_doc = XSLT_DOC_DEFAULT_LOADER(
155             c_uri, c_dict, parse_options, c_ctxt, c_type)
156         if c_doc is NULL:
157             _xslt_store_resolver_exception(c_uri, c_pcontext, c_type)
158
159     if c_doc is not NULL and c_type == xslt.XSLT_LOAD_STYLESHEET:
160         c_doc._private = c_pcontext
161     return c_doc
162
163 cdef xslt.xsltDocLoaderFunc XSLT_DOC_DEFAULT_LOADER = xslt.xsltDocDefaultLoader
164 xslt.xsltSetLoaderFunc(<xslt.xsltDocLoaderFunc>_xslt_doc_loader)
165
166 ################################################################################
167 # XSLT file/network access control
168
169 cdef class XSLTAccessControl:
170     u"""XSLTAccessControl(self, read_file=True, write_file=True, create_dir=True, read_network=True, write_network=True)
171
172     Access control for XSLT: reading/writing files, directories and
173     network I/O.  Access to a type of resource is granted or denied by
174     passing any of the following boolean keyword arguments.  All of
175     them default to True to allow access.
176
177     - read_file
178     - write_file
179     - create_dir
180     - read_network
181     - write_network
182
183     For convenience, there is also a class member `DENY_ALL` that
184     provides an XSLTAccessControl instance that is readily configured
185     to deny everything, and a `DENY_WRITE` member that denies all
186     write access but allows read access.
187
188     See `XSLT`.
189     """
190     cdef xslt.xsltSecurityPrefs* _prefs
191     def __cinit__(self):
192         self._prefs = xslt.xsltNewSecurityPrefs()
193         if self._prefs is NULL:
194             raise MemoryError()
195
196     def __init__(self, *, bint read_file=True, bint write_file=True, bint create_dir=True,
197                  bint read_network=True, bint write_network=True):
198         self._setAccess(xslt.XSLT_SECPREF_READ_FILE, read_file)
199         self._setAccess(xslt.XSLT_SECPREF_WRITE_FILE, write_file)
200         self._setAccess(xslt.XSLT_SECPREF_CREATE_DIRECTORY, create_dir)
201         self._setAccess(xslt.XSLT_SECPREF_READ_NETWORK, read_network)
202         self._setAccess(xslt.XSLT_SECPREF_WRITE_NETWORK, write_network)
203
204     DENY_ALL = XSLTAccessControl(
205         read_file=False, write_file=False, create_dir=False,
206         read_network=False, write_network=False)
207
208     DENY_WRITE = XSLTAccessControl(
209         read_file=True, write_file=False, create_dir=False,
210         read_network=True, write_network=False)
211
212     def __dealloc__(self):
213         if self._prefs is not NULL:
214             xslt.xsltFreeSecurityPrefs(self._prefs)
215
216     @cython.final
217     cdef _setAccess(self, xslt.xsltSecurityOption option, bint allow):
218         cdef xslt.xsltSecurityCheck function
219         if allow:
220             function = xslt.xsltSecurityAllow
221         else:
222             function = xslt.xsltSecurityForbid
223         xslt.xsltSetSecurityPrefs(self._prefs, option, function)
224
225     @cython.final
226     cdef void _register_in_context(self, xslt.xsltTransformContext* ctxt):
227         xslt.xsltSetCtxtSecurityPrefs(self._prefs, ctxt)
228
229     @property
230     def options(self):
231         """The access control configuration as a map of options."""
232         return {
233             u'read_file': self._optval(xslt.XSLT_SECPREF_READ_FILE),
234             u'write_file': self._optval(xslt.XSLT_SECPREF_WRITE_FILE),
235             u'create_dir': self._optval(xslt.XSLT_SECPREF_CREATE_DIRECTORY),
236             u'read_network': self._optval(xslt.XSLT_SECPREF_READ_NETWORK),
237             u'write_network': self._optval(xslt.XSLT_SECPREF_WRITE_NETWORK),
238         }
239
240     @cython.final
241     cdef _optval(self, xslt.xsltSecurityOption option):
242         cdef xslt.xsltSecurityCheck function
243         function = xslt.xsltGetSecurityPrefs(self._prefs, option)
244         if function is <xslt.xsltSecurityCheck>xslt.xsltSecurityAllow:
245             return True
246         elif function is <xslt.xsltSecurityCheck>xslt.xsltSecurityForbid:
247             return False
248         else:
249             return None
250
251     def __repr__(self):
252         items = sorted(self.options.items())
253         return u"%s(%s)" % (
254             python._fqtypename(self).decode('UTF-8').split(u'.')[-1],
255             u', '.join([u"%s=%r" % item for item in items]))
256
257 ################################################################################
258 # XSLT
259
260 cdef int _register_xslt_function(void* ctxt, name_utf, ns_utf):
261     if ns_utf is None:
262         return 0
263     # libxml2 internalises the strings if ctxt has a dict
264     return xslt.xsltRegisterExtFunction(
265         <xslt.xsltTransformContext*>ctxt, _xcstr(name_utf), _xcstr(ns_utf),
266         <xslt.xmlXPathFunction>_xpath_function_call)
267
268 cdef dict EMPTY_DICT = {}
269
270 @cython.final
271 @cython.internal
272 cdef class _XSLTContext(_BaseContext):
273     cdef xslt.xsltTransformContext* _xsltCtxt
274     cdef _ReadOnlyElementProxy _extension_element_proxy
275     cdef dict _extension_elements
276     def __cinit__(self):
277         self._xsltCtxt = NULL
278         self._extension_elements = EMPTY_DICT
279
280     def __init__(self, namespaces, extensions, error_log, enable_regexp,
281                  build_smart_strings):
282         if extensions is not None and extensions:
283             for ns_name_tuple, extension in extensions.items():
284                 if ns_name_tuple[0] is None:
285                     raise XSLTExtensionError, \
286                         u"extensions must not have empty namespaces"
287                 if isinstance(extension, XSLTExtension):
288                     if self._extension_elements is EMPTY_DICT:
289                         self._extension_elements = {}
290                         extensions = extensions.copy()
291                     ns_utf   = _utf8(ns_name_tuple[0])
292                     name_utf = _utf8(ns_name_tuple[1])
293                     self._extension_elements[(ns_utf, name_utf)] = extension
294                     del extensions[ns_name_tuple]
295         _BaseContext.__init__(self, namespaces, extensions, error_log, enable_regexp,
296                               build_smart_strings)
297
298     cdef _BaseContext _copy(self):
299         cdef _XSLTContext context
300         context = <_XSLTContext>_BaseContext._copy(self)
301         context._extension_elements = self._extension_elements
302         return context
303
304     cdef register_context(self, xslt.xsltTransformContext* xsltCtxt,
305                                _Document doc):
306         self._xsltCtxt = xsltCtxt
307         self._set_xpath_context(xsltCtxt.xpathCtxt)
308         self._register_context(doc)
309         self.registerLocalFunctions(xsltCtxt, _register_xslt_function)
310         self.registerGlobalFunctions(xsltCtxt, _register_xslt_function)
311         _registerXSLTExtensions(xsltCtxt, self._extension_elements)
312
313     cdef free_context(self):
314         self._cleanup_context()
315         self._release_context()
316         if self._xsltCtxt is not NULL:
317             xslt.xsltFreeTransformContext(self._xsltCtxt)
318             self._xsltCtxt = NULL
319         self._release_temp_refs()
320
321
322 @cython.final
323 @cython.internal
324 @cython.freelist(8)
325 cdef class _XSLTQuotedStringParam:
326     u"""A wrapper class for literal XSLT string parameters that require
327     quote escaping.
328     """
329     cdef bytes strval
330     def __cinit__(self, strval):
331         self.strval = _utf8(strval)
332
333
334 @cython.no_gc_clear
335 cdef class XSLT:
336     u"""XSLT(self, xslt_input, extensions=None, regexp=True, access_control=None)
337
338     Turn an XSL document into an XSLT object.
339
340     Calling this object on a tree or Element will execute the XSLT::
341
342         transform = etree.XSLT(xsl_tree)
343         result = transform(xml_tree)
344
345     Keyword arguments of the constructor:
346
347     - extensions: a dict mapping ``(namespace, name)`` pairs to
348       extension functions or extension elements
349     - regexp: enable exslt regular expression support in XPath
350       (default: True)
351     - access_control: access restrictions for network or file
352       system (see `XSLTAccessControl`)
353
354     Keyword arguments of the XSLT call:
355
356     - profile_run: enable XSLT profiling (default: False)
357
358     Other keyword arguments of the call are passed to the stylesheet
359     as parameters.
360     """
361     cdef _XSLTContext _context
362     cdef xslt.xsltStylesheet* _c_style
363     cdef _XSLTResolverContext _xslt_resolver_context
364     cdef XSLTAccessControl _access_control
365     cdef _ErrorLog _error_log
366
367     def __cinit__(self):
368         self._c_style = NULL
369
370     def __init__(self, xslt_input, *, extensions=None, regexp=True,
371                  access_control=None):
372         cdef xslt.xsltStylesheet* c_style = NULL
373         cdef xmlDoc* c_doc
374         cdef _Document doc
375         cdef _Element root_node
376
377         doc = _documentOrRaise(xslt_input)
378         root_node = _rootNodeOrRaise(xslt_input)
379
380         # set access control or raise TypeError
381         self._access_control = access_control
382
383         # make a copy of the document as stylesheet parsing modifies it
384         c_doc = _copyDocRoot(doc._c_doc, root_node._c_node)
385
386         # make sure we always have a stylesheet URL
387         if c_doc.URL is NULL:
388             doc_url_utf = python.PyUnicode_AsASCIIString(
389                 f"string://__STRING__XSLT__/{id(self)}.xslt")
390             c_doc.URL = tree.xmlStrdup(_xcstr(doc_url_utf))
391
392         self._error_log = _ErrorLog()
393         self._xslt_resolver_context = _XSLTResolverContext()
394         _initXSLTResolverContext(self._xslt_resolver_context, doc._parser)
395         # keep a copy in case we need to access the stylesheet via 'document()'
396         self._xslt_resolver_context._c_style_doc = _copyDoc(c_doc, 1)
397         c_doc._private = <python.PyObject*>self._xslt_resolver_context
398
399         with self._error_log:
400             c_style = xslt.xsltParseStylesheetDoc(c_doc)
401
402         if c_style is NULL or c_style.errors:
403             tree.xmlFreeDoc(c_doc)
404             if c_style is not NULL:
405                 xslt.xsltFreeStylesheet(c_style)
406             self._xslt_resolver_context._raise_if_stored()
407             # last error seems to be the most accurate here
408             if self._error_log.last_error is not None and \
409                     self._error_log.last_error.message:
410                 raise XSLTParseError(self._error_log.last_error.message,
411                                      self._error_log)
412             else:
413                 raise XSLTParseError(
414                     self._error_log._buildExceptionMessage(
415                         u"Cannot parse stylesheet"),
416                     self._error_log)
417
418         c_doc._private = NULL # no longer used!
419         self._c_style = c_style
420         self._context = _XSLTContext(None, extensions, self._error_log, regexp, True)
421
422     def __dealloc__(self):
423         if self._xslt_resolver_context is not None and \
424                self._xslt_resolver_context._c_style_doc is not NULL:
425             tree.xmlFreeDoc(self._xslt_resolver_context._c_style_doc)
426         # this cleans up the doc copy as well
427         if self._c_style is not NULL:
428             xslt.xsltFreeStylesheet(self._c_style)
429
430     @property
431     def error_log(self):
432         """The log of errors and warnings of an XSLT execution."""
433         return self._error_log.copy()
434
435     @staticmethod
436     def strparam(strval):
437         u"""strparam(strval)
438
439         Mark an XSLT string parameter that requires quote escaping
440         before passing it into the transformation.  Use it like this::
441
442             result = transform(doc, some_strval = XSLT.strparam(
443                 '''it's \"Monty Python's\" ...'''))
444
445         Escaped string parameters can be reused without restriction.
446         """
447         return _XSLTQuotedStringParam(strval)
448
449     @staticmethod
450     def set_global_max_depth(int max_depth):
451         u"""set_global_max_depth(max_depth)
452
453         The maximum traversal depth that the stylesheet engine will allow.
454         This does not only count the template recursion depth but also takes
455         the number of variables/parameters into account.  The required setting
456         for a run depends on both the stylesheet and the input data.
457
458         Example::
459
460             XSLT.set_global_max_depth(5000)
461
462         Note that this is currently a global, module-wide setting because
463         libxslt does not support it at a per-stylesheet level.
464         """
465         if max_depth < 0:
466             raise ValueError("cannot set a maximum stylesheet traversal depth < 0")
467         xslt.xsltMaxDepth = max_depth
468
469     def apply(self, _input, *, profile_run=False, **kw):
470         u"""apply(self, _input,  profile_run=False, **kw)
471         
472         :deprecated: call the object, not this method."""
473         return self(_input, profile_run=profile_run, **kw)
474
475     def tostring(self, _ElementTree result_tree):
476         u"""tostring(self, result_tree)
477
478         Save result doc to string based on stylesheet output method.
479
480         :deprecated: use str(result_tree) instead.
481         """
482         return str(result_tree)
483
484     def __deepcopy__(self, memo):
485         return self.__copy__()
486
487     def __copy__(self):
488         return _copyXSLT(self)
489
490     def __call__(self, _input, *, profile_run=False, **kw):
491         u"""__call__(self, _input, profile_run=False, **kw)
492
493         Execute the XSL transformation on a tree or Element.
494
495         Pass the ``profile_run`` option to get profile information
496         about the XSLT.  The result of the XSLT will have a property
497         xslt_profile that holds an XML tree with profiling data.
498         """
499         cdef _XSLTContext context = None
500         cdef _XSLTResolverContext resolver_context
501         cdef _Document input_doc
502         cdef _Element root_node
503         cdef _Document result_doc
504         cdef _Document profile_doc = None
505         cdef xmlDoc* c_profile_doc
506         cdef xslt.xsltTransformContext* transform_ctxt
507         cdef xmlDoc* c_result = NULL
508         cdef xmlDoc* c_doc
509         cdef tree.xmlDict* c_dict
510         cdef const_char** params = NULL
511
512         assert self._c_style is not NULL, "XSLT stylesheet not initialised"
513         input_doc = _documentOrRaise(_input)
514         root_node = _rootNodeOrRaise(_input)
515
516         c_doc = _fakeRootDoc(input_doc._c_doc, root_node._c_node)
517
518         transform_ctxt = xslt.xsltNewTransformContext(self._c_style, c_doc)
519         if transform_ctxt is NULL:
520             _destroyFakeDoc(input_doc._c_doc, c_doc)
521             raise MemoryError()
522
523         # using the stylesheet dict is safer than using a possibly
524         # unrelated dict from the current thread.  Almost all
525         # non-input tag/attr names will come from the stylesheet
526         # anyway.
527         if transform_ctxt.dict is not NULL:
528             xmlparser.xmlDictFree(transform_ctxt.dict)
529         if kw:
530             # parameter values are stored in the dict
531             # => avoid unnecessarily cluttering the global dict
532             transform_ctxt.dict = xmlparser.xmlDictCreateSub(self._c_style.doc.dict)
533             if transform_ctxt.dict is NULL:
534                 xslt.xsltFreeTransformContext(transform_ctxt)
535                 raise MemoryError()
536         else:
537             transform_ctxt.dict = self._c_style.doc.dict
538             xmlparser.xmlDictReference(transform_ctxt.dict)
539
540         xslt.xsltSetCtxtParseOptions(
541             transform_ctxt, input_doc._parser._parse_options)
542
543         if profile_run:
544             transform_ctxt.profile = 1
545
546         try:
547             context = self._context._copy()
548             context.register_context(transform_ctxt, input_doc)
549
550             resolver_context = self._xslt_resolver_context._copy()
551             transform_ctxt._private = <python.PyObject*>resolver_context
552
553             _convert_xslt_parameters(transform_ctxt, kw, &params)
554             c_result = self._run_transform(
555                 c_doc, params, context, transform_ctxt)
556             if params is not NULL:
557                 # deallocate space for parameters
558                 python.lxml_free(params)
559
560             if transform_ctxt.state != xslt.XSLT_STATE_OK:
561                 if c_result is not NULL:
562                     tree.xmlFreeDoc(c_result)
563                     c_result = NULL
564
565             if transform_ctxt.profile:
566                 c_profile_doc = xslt.xsltGetProfileInformation(transform_ctxt)
567                 if c_profile_doc is not NULL:
568                     profile_doc = _documentFactory(
569                         c_profile_doc, input_doc._parser)
570         finally:
571             if context is not None:
572                 context.free_context()
573             _destroyFakeDoc(input_doc._c_doc, c_doc)
574
575         try:
576             if resolver_context is not None and resolver_context._has_raised():
577                 if c_result is not NULL:
578                     tree.xmlFreeDoc(c_result)
579                     c_result = NULL
580                 resolver_context._raise_if_stored()
581
582             if context._exc._has_raised():
583                 if c_result is not NULL:
584                     tree.xmlFreeDoc(c_result)
585                     c_result = NULL
586                 context._exc._raise_if_stored()
587
588             if c_result is NULL:
589                 # last error seems to be the most accurate here
590                 error = self._error_log.last_error
591                 if error is not None and error.message:
592                     if error.line > 0:
593                         message = f"{error.message}, line {error.line}"
594                     else:
595                         message = error.message
596                 elif error is not None and error.line > 0:
597                     message = f"Error applying stylesheet, line {error.line}"
598                 else:
599                     message = u"Error applying stylesheet"
600                 raise XSLTApplyError(message, self._error_log)
601         finally:
602             if resolver_context is not None:
603                 resolver_context.clear()
604
605         result_doc = _documentFactory(c_result, input_doc._parser)
606
607         c_dict = c_result.dict
608         xmlparser.xmlDictReference(c_dict)
609         __GLOBAL_PARSER_CONTEXT.initThreadDictRef(&c_result.dict)
610         if c_dict is not c_result.dict or \
611                 self._c_style.doc.dict is not c_result.dict or \
612                 input_doc._c_doc.dict is not c_result.dict:
613             with nogil:
614                 if c_dict is not c_result.dict:
615                     fixThreadDictNames(<xmlNode*>c_result,
616                                        c_dict, c_result.dict)
617                 if self._c_style.doc.dict is not c_result.dict:
618                     fixThreadDictNames(<xmlNode*>c_result,
619                                        self._c_style.doc.dict, c_result.dict)
620                 if input_doc._c_doc.dict is not c_result.dict:
621                     fixThreadDictNames(<xmlNode*>c_result,
622                                        input_doc._c_doc.dict, c_result.dict)
623         xmlparser.xmlDictFree(c_dict)
624
625         return _xsltResultTreeFactory(result_doc, self, profile_doc)
626
627     cdef xmlDoc* _run_transform(self, xmlDoc* c_input_doc,
628                                 const_char** params, _XSLTContext context,
629                                 xslt.xsltTransformContext* transform_ctxt):
630         cdef xmlDoc* c_result
631         xslt.xsltSetTransformErrorFunc(transform_ctxt, <void*>self._error_log,
632                                        <xmlerror.xmlGenericErrorFunc>_receiveXSLTError)
633         if self._access_control is not None:
634             self._access_control._register_in_context(transform_ctxt)
635         with self._error_log, nogil:
636             c_result = xslt.xsltApplyStylesheetUser(
637                 self._c_style, c_input_doc, params, NULL, NULL, transform_ctxt)
638         return c_result
639
640
641 cdef _convert_xslt_parameters(xslt.xsltTransformContext* transform_ctxt,
642                               dict parameters, const_char*** params_ptr):
643     cdef Py_ssize_t i, parameter_count
644     cdef const_char** params
645     cdef tree.xmlDict* c_dict = transform_ctxt.dict
646     params_ptr[0] = NULL
647     parameter_count = len(parameters)
648     if parameter_count == 0:
649         return
650     # allocate space for parameters
651     # * 2 as we want an entry for both key and value,
652     # and + 1 as array is NULL terminated
653     params = <const_char**>python.lxml_malloc(parameter_count * 2 + 1, sizeof(const_char*))
654     if not params:
655         raise MemoryError()
656     try:
657         i = 0
658         for key, value in parameters.iteritems():
659             k = _utf8(key)
660             if isinstance(value, _XSLTQuotedStringParam):
661                 v = (<_XSLTQuotedStringParam>value).strval
662                 xslt.xsltQuoteOneUserParam(
663                     transform_ctxt, _xcstr(k), _xcstr(v))
664             else:
665                 if isinstance(value, XPath):
666                     v = (<XPath>value)._path
667                 else:
668                     v = _utf8(value)
669                 params[i] = <const_char*>tree.xmlDictLookup(c_dict, _xcstr(k), len(k))
670                 i += 1
671                 params[i] = <const_char*>tree.xmlDictLookup(c_dict, _xcstr(v), len(v))
672                 i += 1
673     except:
674         python.lxml_free(params)
675         raise
676     params[i] = NULL
677     params_ptr[0] = params
678
679 cdef XSLT _copyXSLT(XSLT stylesheet):
680     cdef XSLT new_xslt
681     cdef xmlDoc* c_doc
682     assert stylesheet._c_style is not NULL, "XSLT stylesheet not initialised"
683     new_xslt = XSLT.__new__(XSLT)
684     new_xslt._access_control = stylesheet._access_control
685     new_xslt._error_log = _ErrorLog()
686     new_xslt._context = stylesheet._context._copy()
687
688     new_xslt._xslt_resolver_context = stylesheet._xslt_resolver_context._copy()
689     new_xslt._xslt_resolver_context._c_style_doc = _copyDoc(
690         stylesheet._xslt_resolver_context._c_style_doc, 1)
691
692     c_doc = _copyDoc(stylesheet._c_style.doc, 1)
693     new_xslt._c_style = xslt.xsltParseStylesheetDoc(c_doc)
694     if new_xslt._c_style is NULL:
695         tree.xmlFreeDoc(c_doc)
696         raise MemoryError()
697
698     return new_xslt
699
700 @cython.final
701 cdef class _XSLTResultTree(_ElementTree):
702     """The result of an XSLT evaluation.
703
704     Use ``str()`` or ``bytes()`` (or ``unicode()`` in Python 2.x) to serialise to a string,
705     and the ``.write_output()`` method to write serialise to a file.
706     """
707     cdef XSLT _xslt
708     cdef _Document _profile
709     cdef xmlChar* _buffer
710     cdef Py_ssize_t _buffer_len
711     cdef Py_ssize_t _buffer_refcnt
712
713     def write_output(self, file, *, compression=0):
714         """write_output(self, file, *, compression=0)
715
716         Serialise the XSLT output to a file or file-like object.
717
718         As opposed to the generic ``.write()`` method, ``.write_output()`` serialises
719         the result as defined by the ``<xsl:output>`` tag.
720         """
721         cdef _FilelikeWriter writer = None
722         cdef _Document doc
723         cdef int r, rclose, c_compression
724         cdef const_xmlChar* c_encoding = NULL
725         cdef tree.xmlOutputBuffer* c_buffer
726
727         if self._context_node is not None:
728             doc = self._context_node._doc
729         else:
730             doc = None
731         if doc is None:
732             doc = self._doc
733             if doc is None:
734                 raise XSLTSaveError("No document to serialise")
735         c_compression = compression or 0
736         xslt.LXML_GET_XSLT_ENCODING(c_encoding, self._xslt._c_style)
737         writer = _create_output_buffer(file, <const_char*>c_encoding, compression, &c_buffer, close=False)
738         if writer is None:
739             with nogil:
740                 r = xslt.xsltSaveResultTo(c_buffer, doc._c_doc, self._xslt._c_style)
741                 rclose = tree.xmlOutputBufferClose(c_buffer)
742         else:
743             r = xslt.xsltSaveResultTo(c_buffer, doc._c_doc, self._xslt._c_style)
744             rclose = tree.xmlOutputBufferClose(c_buffer)
745         if writer is not None:
746             writer._exc_context._raise_if_stored()
747         if r < 0 or rclose == -1:
748             python.PyErr_SetFromErrno(IOError)  # raises IOError
749
750     cdef _saveToStringAndSize(self, xmlChar** s, int* l):
751         cdef _Document doc
752         cdef int r
753         if self._context_node is not None:
754             doc = self._context_node._doc
755         else:
756             doc = None
757         if doc is None:
758             doc = self._doc
759             if doc is None:
760                 s[0] = NULL
761                 return
762         with nogil:
763             r = xslt.xsltSaveResultToString(s, l, doc._c_doc,
764                                             self._xslt._c_style)
765         if r == -1:
766             raise MemoryError()
767
768     def __str__(self):
769         cdef xmlChar* s = NULL
770         cdef int l = 0
771         if not python.IS_PYTHON2:
772             return self.__unicode__()
773         self._saveToStringAndSize(&s, &l)
774         if s is NULL:
775             return ''
776         # we must not use 'funicode()' here as this is not always UTF-8
777         try:
778             result = <bytes>s[:l]
779         finally:
780             tree.xmlFree(s)
781         return result
782
783     def __unicode__(self):
784         cdef xmlChar* encoding
785         cdef xmlChar* s = NULL
786         cdef int l = 0
787         self._saveToStringAndSize(&s, &l)
788         if s is NULL:
789             return u''
790         encoding = self._xslt._c_style.encoding
791         try:
792             if encoding is NULL:
793                 result = s[:l].decode('UTF-8')
794             else:
795                 result = s[:l].decode(encoding)
796         finally:
797             tree.xmlFree(s)
798         return _stripEncodingDeclaration(result)
799
800     def __getbuffer__(self, Py_buffer* buffer, int flags):
801         cdef int l = 0
802         if buffer is NULL:
803             return
804         if self._buffer is NULL or flags & python.PyBUF_WRITABLE:
805             self._saveToStringAndSize(<xmlChar**>&buffer.buf, &l)
806             buffer.len = l
807             if self._buffer is NULL and not flags & python.PyBUF_WRITABLE:
808                 self._buffer = <xmlChar*>buffer.buf
809                 self._buffer_len = l
810                 self._buffer_refcnt = 1
811         else:
812             buffer.buf = self._buffer
813             buffer.len = self._buffer_len
814             self._buffer_refcnt += 1
815         if flags & python.PyBUF_WRITABLE:
816             buffer.readonly = 0
817         else:
818             buffer.readonly = 1
819         if flags & python.PyBUF_FORMAT:
820             buffer.format = "B"
821         else:
822             buffer.format = NULL
823         buffer.ndim = 0
824         buffer.shape = NULL
825         buffer.strides = NULL
826         buffer.suboffsets = NULL
827         buffer.itemsize = 1
828         buffer.internal = NULL
829         if buffer.obj is not self: # set by Cython?
830             buffer.obj = self
831
832     def __releasebuffer__(self, Py_buffer* buffer):
833         if buffer is NULL:
834             return
835         if <xmlChar*>buffer.buf is self._buffer:
836             self._buffer_refcnt -= 1
837             if self._buffer_refcnt == 0:
838                 tree.xmlFree(<char*>self._buffer)
839                 self._buffer = NULL
840         else:
841             tree.xmlFree(<char*>buffer.buf)
842         buffer.buf = NULL
843
844     property xslt_profile:
845         """Return an ElementTree with profiling data for the stylesheet run.
846         """
847         def __get__(self):
848             cdef object root
849             if self._profile is None:
850                 return None
851             root = self._profile.getroot()
852             if root is None:
853                 return None
854             return ElementTree(root)
855
856         def __del__(self):
857             self._profile = None
858
859 cdef _xsltResultTreeFactory(_Document doc, XSLT xslt, _Document profile):
860     cdef _XSLTResultTree result
861     result = <_XSLTResultTree>_newElementTree(doc, None, _XSLTResultTree)
862     result._xslt = xslt
863     result._profile = profile
864     return result
865
866 # functions like "output" and "write" are a potential security risk, but we
867 # rely on the user to configure XSLTAccessControl as needed
868 xslt.xsltRegisterAllExtras()
869
870 # enable EXSLT support for XSLT
871 xslt.exsltRegisterAll()
872
873
874 ################################################################################
875 # XSLT PI support
876
877 cdef object _RE_PI_HREF = re.compile(ur'\s+href\s*=\s*(?:\'([^\']*)\'|"([^"]*)")')
878 cdef object _FIND_PI_HREF = _RE_PI_HREF.findall
879 cdef object _REPLACE_PI_HREF = _RE_PI_HREF.sub
880 cdef XPath __findStylesheetByID = None
881
882 cdef _findStylesheetByID(_Document doc, id):
883     global __findStylesheetByID
884     if __findStylesheetByID is None:
885         __findStylesheetByID = XPath(
886             u"//xsl:stylesheet[@xml:id = $id]",
887             namespaces={u"xsl" : u"http://www.w3.org/1999/XSL/Transform"})
888     return __findStylesheetByID(doc, id=id)
889
890 cdef class _XSLTProcessingInstruction(PIBase):
891     def parseXSL(self, parser=None):
892         u"""parseXSL(self, parser=None)
893
894         Try to parse the stylesheet referenced by this PI and return
895         an ElementTree for it.  If the stylesheet is embedded in the
896         same document (referenced via xml:id), find and return an
897         ElementTree for the stylesheet Element.
898
899         The optional ``parser`` keyword argument can be passed to specify the
900         parser used to read from external stylesheet URLs.
901         """
902         cdef _Document result_doc
903         cdef _Element  result_node
904         cdef bytes href_utf
905         cdef const_xmlChar* c_href
906         cdef xmlAttr* c_attr
907         _assertValidNode(self)
908         if self._c_node.content is NULL:
909             raise ValueError, u"PI lacks content"
910         hrefs = _FIND_PI_HREF(u' ' + (<unsigned char*>self._c_node.content).decode('UTF-8'))
911         if len(hrefs) != 1:
912             raise ValueError, u"malformed PI attributes"
913         hrefs = hrefs[0]
914         href_utf = utf8(hrefs[0] or hrefs[1])
915         c_href = _xcstr(href_utf)
916
917         if c_href[0] != c'#':
918             # normal URL, try to parse from it
919             c_href = tree.xmlBuildURI(
920                 c_href,
921                 tree.xmlNodeGetBase(self._c_node.doc, self._c_node))
922             if c_href is not NULL:
923                 try:
924                     href_utf = <unsigned char*>c_href
925                 finally:
926                     tree.xmlFree(<char*>c_href)
927             result_doc = _parseDocumentFromURL(href_utf, parser)
928             return _elementTreeFactory(result_doc, None)
929
930         # ID reference to embedded stylesheet
931         # try XML:ID lookup
932         _assertValidDoc(self._doc)
933         c_href += 1 # skip leading '#'
934         c_attr = tree.xmlGetID(self._c_node.doc, c_href)
935         if c_attr is not NULL and c_attr.doc is self._c_node.doc:
936             result_node = _elementFactory(self._doc, c_attr.parent)
937             return _elementTreeFactory(result_node._doc, result_node)
938
939         # try XPath search
940         root = _findStylesheetByID(self._doc, funicode(c_href))
941         if not root:
942             raise ValueError, u"reference to non-existing embedded stylesheet"
943         elif len(root) > 1:
944             raise ValueError, u"ambiguous reference to embedded stylesheet"
945         result_node = root[0]
946         return _elementTreeFactory(result_node._doc, result_node)
947
948     def set(self, key, value):
949         u"""set(self, key, value)
950
951         Supports setting the 'href' pseudo-attribute in the text of
952         the processing instruction.
953         """
954         if key != u"href":
955             raise AttributeError, \
956                 u"only setting the 'href' attribute is supported on XSLT-PIs"
957         if value is None:
958             attrib = u""
959         elif u'"' in value or u'>' in value:
960             raise ValueError, u"Invalid URL, must not contain '\"' or '>'"
961         else:
962             attrib = f' href="{value}"'
963         text = u' ' + self.text
964         if _FIND_PI_HREF(text):
965             self.text = _REPLACE_PI_HREF(attrib, text)
966         else:
967             self.text = text + attrib