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