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