[Lxml-checkins] r52026 - in lxml/trunk: . doc src/lxml src/lxml/tests
scoder at codespeak.net
scoder at codespeak.net
Sun Mar 2 09:31:28 CET 2008
Author: scoder
Date: Sun Mar 2 09:31:28 2008
New Revision: 52026
Added:
lxml/trunk/src/lxml/xsltext.pxi
Modified:
lxml/trunk/ (props changed)
lxml/trunk/CHANGES.txt
lxml/trunk/doc/extensions.txt
lxml/trunk/doc/xpathxslt.txt
lxml/trunk/src/lxml/lxml.etree.pyx
lxml/trunk/src/lxml/readonlytree.pxi
lxml/trunk/src/lxml/tests/test_xslt.py
lxml/trunk/src/lxml/xslt.pxd
lxml/trunk/src/lxml/xslt.pxi
Log:
r3665 at delle: sbehnel | 2008-03-02 08:56:20 +0100
r3655 at delle: sbehnel | 2008-03-01 22:27:17 +0100
partial reimplementation of the extension element mechanism, works now
Modified: lxml/trunk/CHANGES.txt
==============================================================================
--- lxml/trunk/CHANGES.txt (original)
+++ lxml/trunk/CHANGES.txt Sun Mar 2 09:31:28 2008
@@ -8,6 +8,8 @@
Features added
--------------
+* Extension elements for XSLT
+
* ``Element.base`` property returns the xml:base or HTML base URL of
an Element.
Modified: lxml/trunk/doc/extensions.txt
==============================================================================
--- lxml/trunk/doc/extensions.txt (original)
+++ lxml/trunk/doc/extensions.txt Sun Mar 2 09:31:28 2008
@@ -1,15 +1,24 @@
-Extension functions for XPath and XSLT
-======================================
+Python extensions for XPath and XSLT
+====================================
-This document describes how to use Python extension functions in XPath and
-XSLT. They allow you to do things like this::
+This document describes how to use Python extension functions in XPath
+and XSLT like this::
<xsl:value-of select="f:myPythonFunction(.//sometag)" />
-Here is how such a function looks like. As the first argument, it always
-receives a context object (see below). The other arguments are provided by
-the respective call in the XPath expression, one in the following examples.
-Any number of arguments is allowed::
+It also describes how to use Python extension elements in XSLT like
+this::
+
+ <xsl:template match="*">
+ <my:python-extension>
+ <some-content />
+ </my:python-extension>
+ </xsl:template>
+
+Here is how an extension function looks like. As the first argument,
+it always receives a context object (see below). The other arguments
+are provided by the respective call in the XPath expression, one in
+the following examples. Any number of arguments is allowed::
>>> def hello(dummy, a):
... return "Hello %s" % a
@@ -18,14 +27,23 @@
>>> def loadsofargs(dummy, *args):
... return "Got %d arguments." % len(args)
+And here is how an extension element looks like::
+
+ >>> from lxml import etree
+ >>> class MyExtElement(etree.XSLTExtension):
+ ... def execute(self, context, self_node, input_node, output_parent):
+ ... # just copy own content input to output
+ ... output_parent.extend( list(self_node) )
+
.. contents::
..
1 The FunctionNamespace
2 Global prefix assignment
- 3 Evaluators and XSLT
- 4 Evaluator-local extensions
- 5 What to return from a function
+ 3 The XPath context
+ 4 Evaluators and XSLT
+ 5 Evaluator-local extensions
+ 6 What to return from a function
The FunctionNamespace
@@ -36,7 +54,6 @@
FunctionNamespace class. For simplicity, we choose the empty namespace
(None)::
- >>> from lxml import etree
>>> ns = etree.FunctionNamespace(None)
>>> ns['hello'] = hello
>>> ns['countargs'] = loadsofargs
Modified: lxml/trunk/doc/xpathxslt.txt
==============================================================================
--- lxml/trunk/doc/xpathxslt.txt (original)
+++ lxml/trunk/doc/xpathxslt.txt Sun Mar 2 09:31:28 2008
@@ -454,6 +454,14 @@
'<?xml version="1.0"?>\n<foo>Text</foo>\n'
+Extension elements
+------------------
+
+Just like `custom extension functions`_, lxml supports custom
+extension *elements*.
+
+
+
The ``xslt()`` tree method
--------------------------
Modified: lxml/trunk/src/lxml/lxml.etree.pyx
==============================================================================
--- lxml/trunk/src/lxml/lxml.etree.pyx (original)
+++ lxml/trunk/src/lxml/lxml.etree.pyx Sun Mar 2 09:31:28 2008
@@ -2578,9 +2578,15 @@
include "iterparse.pxi" # incremental XML parsing
include "xmlid.pxi" # XMLID and IDDict
include "xinclude.pxi" # XInclude
+
+
+################################################################################
+# Include submodules for XPath and XSLT
+
include "extensions.pxi" # XPath/XSLT extension functions
include "xpath.pxi" # XPath evaluation
include "xslt.pxi" # XSL transformations
+include "xsltext.pxi" # XSL extension elements
################################################################################
Modified: lxml/trunk/src/lxml/readonlytree.pxi
==============================================================================
--- lxml/trunk/src/lxml/readonlytree.pxi (original)
+++ lxml/trunk/src/lxml/readonlytree.pxi Sun Mar 2 09:31:28 2008
@@ -2,6 +2,7 @@
cdef class _ReadOnlyElementProxy:
"The main read-only Element proxy class (for internal use only!)."
+ cdef bint _free_after_use
cdef xmlNode* _c_node
cdef object _source_proxy
cdef object _dependent_proxies
@@ -12,6 +13,11 @@
assert self._c_node is not NULL, "Proxy invalidated!"
return 0
+ cdef void free_after_use(self):
+ """Should the xmlNode* be freed when releasing the proxy?
+ """
+ self._free_after_use = 1
+
property tag:
"""Element tag
"""
@@ -216,6 +222,7 @@
cdef inline _initReadOnlyProxy(_ReadOnlyElementProxy el,
_ReadOnlyElementProxy source_proxy):
+ el._free_after_use = 0
if source_proxy is None:
el._source_proxy = el
el._dependent_proxies = [el]
@@ -224,23 +231,19 @@
python.PyList_Append(source_proxy._dependent_proxies, el)
cdef _freeReadOnlyProxies(_ReadOnlyElementProxy sourceProxy):
+ cdef xmlNode* c_node
cdef _ReadOnlyElementProxy el
if sourceProxy is None:
return
if sourceProxy._dependent_proxies is None:
return
for el in sourceProxy._dependent_proxies:
+ c_node = el._c_node
el._c_node = NULL
+ if el._free_after_use:
+ tree.xmlFreeNode(c_node)
del sourceProxy._dependent_proxies[:]
-
-cdef class _ReadOnlyRootElementProxy(_ReadOnlyElementProxy):
- """A read-only element that frees the subtree on deallocation.
- """
- def __dealloc__(self):
- if self._c_node is not NULL:
- tree.xmlFreeNode(self._c_node)
-
cdef class _AppendOnlyElementProxy(_ReadOnlyElementProxy):
"""A read-only element that allows adding children and changing the
text content (i.e. everything that adds to the subtree).
Modified: lxml/trunk/src/lxml/tests/test_xslt.py
==============================================================================
--- lxml/trunk/src/lxml/tests/test_xslt.py (original)
+++ lxml/trunk/src/lxml/tests/test_xslt.py Sun Mar 2 09:31:28 2008
@@ -613,17 +613,68 @@
extension-element-prefixes="myns"
exclude-result-prefixes="myns">
<xsl:template match="a">
- <A><myns:mytext>b</myns:mytext></A>
+ <A><myns:myext>b</myns:myext></A>
</xsl:template>
</xsl:stylesheet>''')
- class mytext(etree.XSLTExtension):
- pass
+ class MyExt(etree.XSLTExtension):
+ def execute(self, context, self_node, input_node, output_parent):
+ child = etree.Element(self_node.text)
+ child.text = 'X'
+ output_parent.append(child)
+
+ extensions = { ('testns', 'myext') : MyExt() }
- result = tree.xslt(style, extensions={})
+ result = tree.xslt(style, extensions=extensions)
self.assertEquals(self._rootstring(result),
'<A><b>X</b></A>')
+ def test_extension_element_content(self):
+ tree = self.parse('<a><b>B</b></a>')
+ style = self.parse('''\
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:myns="testns"
+ extension-element-prefixes="myns"
+ exclude-result-prefixes="myns">
+ <xsl:template match="a">
+ <A><myns:myext><x>X</x><y>Y</y><z/></myns:myext></A>
+ </xsl:template>
+</xsl:stylesheet>''')
+
+ class MyExt(etree.XSLTExtension):
+ def execute(self, context, self_node, input_node, output_parent):
+ output_parent.extend(list(self_node)[1:])
+
+ extensions = { ('testns', 'myext') : MyExt() }
+
+ result = tree.xslt(style, extensions=extensions)
+ self.assertEquals(self._rootstring(result),
+ '<A><y>Y</y><z/></A>')
+
+ def test_extension_element_raise(self):
+ tree = self.parse('<a><b>B</b></a>')
+ style = self.parse('''\
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:myns="testns"
+ extension-element-prefixes="myns"
+ exclude-result-prefixes="myns">
+ <xsl:template match="a">
+ <A><myns:myext>b</myns:myext></A>
+ </xsl:template>
+</xsl:stylesheet>''')
+
+ class MyError(Exception):
+ pass
+
+ class MyExt(etree.XSLTExtension):
+ def execute(self, context, self_node, input_node, output_parent):
+ raise MyError("expected!")
+
+ extensions = { ('testns', 'myext') : MyExt() }
+ self.assertRaises(MyError, tree.xslt, style, extensions=extensions)
+
def test_xslt_document_XML(self):
# make sure document('') works from parsed strings
xslt = etree.XSLT(etree.XML("""\
Modified: lxml/trunk/src/lxml/xslt.pxd
==============================================================================
--- lxml/trunk/src/lxml/xslt.pxd (original)
+++ lxml/trunk/src/lxml/xslt.pxd Sun Mar 2 09:31:28 2008
@@ -8,6 +8,11 @@
cdef int LIBXSLT_VERSION
cdef extern from "libxslt/xsltInternals.h":
+ ctypedef enum xsltTransformState:
+ XSLT_STATE_OK # 0
+ XSLT_STATE_ERROR # 1
+ XSLT_STATE_STOPPED # 2
+
ctypedef struct xsltDocument:
xmlDoc* doc
@@ -25,6 +30,7 @@
xmlNode* node
xmlDoc* output
xmlNode* insert
+ xsltTransformState state
ctypedef struct xsltStackElem
@@ -32,6 +38,11 @@
cdef void xsltFreeStylesheet(xsltStylesheet* sheet) nogil
cdef extern from "libxslt/extensions.h":
+ ctypedef void (*xsltTransformFunction)(xsltTransformContext* ctxt,
+ xmlNode* context_node,
+ xmlNode* inst,
+ void* precomp_unused)
+
cdef int xsltRegisterExtFunction(xsltTransformContext* ctxt,
char* name,
char* URI,
@@ -43,6 +54,9 @@
char* name, char* URI) nogil
cdef int xsltRegisterExtPrefix(xsltStylesheet* style,
char* prefix, char* URI) nogil
+ cdef int xsltRegisterExtElement(xsltTransformContext* ctxt,
+ char* name, char* URI,
+ xsltTransformFunction function) nogil
cdef extern from "libxslt/documents.h":
ctypedef enum xsltLoadType:
@@ -82,7 +96,9 @@
cdef void xsltSetTransformErrorFunc(
xsltTransformContext*, void* ctxt,
void (*handler)(void* ctxt, char* msg, ...)) nogil
-
+ cdef void xsltTransformError(xsltTransformContext* ctxt,
+ xsltStylesheet* style,
+ xmlNode* node, char* msg, ...)
cdef extern from "libxslt/security.h":
ctypedef struct xsltSecurityPrefs
ctypedef enum xsltSecurityOption:
Modified: lxml/trunk/src/lxml/xslt.pxi
==============================================================================
--- lxml/trunk/src/lxml/xslt.pxi (original)
+++ lxml/trunk/src/lxml/xslt.pxi Sun Mar 2 09:31:28 2008
@@ -229,15 +229,34 @@
cdef class _XSLTContext(_BaseContext):
cdef xslt.xsltTransformContext* _xsltCtxt
+ cdef object _extension_elements
+ cdef _ReadOnlyElementProxy _extension_element_proxy
def __init__(self, namespaces, extensions, enable_regexp):
self._xsltCtxt = NULL
- if extensions is not None:
- for ns, prefix in extensions:
- if ns is None:
+ self._extension_elements = EMPTY_READ_ONLY_DICT
+ if extensions is not None and extensions:
+ for ns_name_tuple, extension in extensions.items():
+ if ns_name_tuple[0] is None:
raise XSLTExtensionError(
"extensions must not have empty namespaces")
+ if isinstance(extension, XSLTExtension):
+ if self._extension_elements is EMPTY_READ_ONLY_DICT:
+ self._extension_elements = {}
+ extensions = python.PyDict_Copy(extensions)
+ ns_utf = _utf8(ns_name_tuple[0])
+ name_utf = _utf8(ns_name_tuple[1])
+ python.PyDict_SetItem(
+ self._extension_elements, (ns_utf, name_utf),
+ extension)
+ python.PyDict_DelItem(extensions, ns_name_tuple)
_BaseContext.__init__(self, namespaces, extensions, enable_regexp)
+ cdef _BaseContext _copy(self):
+ cdef _XSLTContext context
+ context = <_XSLTContext>_BaseContext._copy(self)
+ context._extension_elements = self._extension_elements
+ return context
+
cdef register_context(self, xslt.xsltTransformContext* xsltCtxt,
_Document doc):
self._xsltCtxt = xsltCtxt
@@ -245,6 +264,7 @@
self._register_context(doc)
self.registerLocalFunctions(xsltCtxt, _register_xslt_function)
self.registerGlobalFunctions(xsltCtxt, _register_xslt_function)
+ _registerXSLTExtensions(xsltCtxt, self._extension_elements)
cdef free_context(self):
self._cleanup_context()
@@ -437,6 +457,11 @@
tree.xmlFreeDoc(c_result)
resolver_context._raise_if_stored()
+ if context._exc._has_raised():
+ if c_result is not NULL:
+ tree.xmlFreeDoc(c_result)
+ context._exc._raise_if_stored()
+
if c_result is NULL:
# last error seems to be the most accurate here
error = self._error_log.last_error
Added: lxml/trunk/src/lxml/xsltext.pxi
==============================================================================
--- (empty file)
+++ lxml/trunk/src/lxml/xsltext.pxi Sun Mar 2 09:31:28 2008
@@ -0,0 +1,111 @@
+# XSLT extension elements
+
+cdef class XSLTExtension:
+ """Base class of an XSLT extension element.
+ """
+ def execute(self, context, self_node, input_node, output_parent):
+ """execute(self, context, self_node, input_node, output_parent)
+ Execute this extension element.
+
+ Subclasses may append elements to the `output_parent` element
+ here, or set its text content. To this end, the `input_node`
+ provides read-only access to the current node in the input
+ document, and the `self_node` points to the extension element
+ in the stylesheet.
+ """
+ pass
+
+ def apply_templates(self, _XSLTContext context not None, node):
+ """apply_templates(self, context, node)
+
+ Call this method to continue applying templates to the input
+ document. Starts at the
+
+ The return value is a list of elements that were generated.
+ """
+ cdef xmlNode* c_parent
+ cdef xmlNode* c_node
+ cdef xmlNode* c_next
+ cdef xmlNode* c_context_node
+ cdef _ReadOnlyElementProxy proxy
+ c_context_node = _roNodeOf(node)
+ #assert c_context_node.doc is context._xsltContext.node.doc, \
+ # "switching input documents during transformation is not currently supported"
+
+ c_parent = tree.xmlNewDocNode(
+ context._xsltCtxt.output, NULL, "fake-parent", NULL)
+
+ c_node = context._xsltCtxt.insert
+ context._xsltCtxt.insert = c_parent
+ xslt.xsltProcessOneNode(
+ context._xsltCtxt, c_context_node, NULL)
+ context._xsltCtxt.insert = c_node
+
+ results = []
+ c_node = c_parent.children
+ try:
+ while c_node is not NULL:
+ c_next = c_node.next
+ tree.xmlUnlinkNode(c_node)
+ proxy = _newReadOnlyProxy(
+ context._extension_element_proxy, c_node)
+ proxy.free_after_use()
+ python.PyList_Append(results, proxy)
+ c_node = c_next
+ finally:
+ tree.xmlFreeNode(c_parent)
+ return results
+
+
+cdef _registerXSLTExtensions(xslt.xsltTransformContext* c_ctxt,
+ extension_dict):
+ for ns, name in extension_dict:
+ xslt.xsltRegisterExtElement(
+ c_ctxt, _cstr(name), _cstr(ns), _callExtensionElement)
+
+cdef void _callExtensionElement(xslt.xsltTransformContext* c_ctxt,
+ xmlNode* c_context_node,
+ xmlNode* c_inst_node,
+ void* dummy) with gil:
+ cdef _XSLTContext context
+ cdef XSLTExtension extension
+ cdef python.PyObject* dict_result
+ cdef char* c_uri
+ cdef _ReadOnlyElementProxy context_node, self_node, output_parent
+ c_uri = _getNs(c_inst_node)
+ if c_uri is NULL:
+ # not allowed, and should never happen
+ return
+ if c_ctxt.xpathCtxt.userData is NULL:
+ # just for safety, should never happen
+ return
+ context = <_XSLTContext>c_ctxt.xpathCtxt.userData
+ try:
+ dict_result = python.PyDict_GetItem(
+ context._extension_elements, (c_uri, c_inst_node.name))
+ if dict_result is NULL:
+ raise KeyError("extension element %s not found",
+ c_inst_node.name)
+ extension = <object>dict_result
+
+ try:
+ self_node = _newReadOnlyProxy(None, c_inst_node)
+ context_node = _newReadOnlyProxy(self_node, c_context_node)
+ output_parent = _newAppendOnlyProxy(self_node, c_ctxt.insert)
+
+ context._extension_element_proxy = self_node
+ extension.execute(context, self_node, context_node, output_parent)
+ finally:
+ context._extension_element_proxy = None
+ if self_node is not None:
+ _freeReadOnlyProxies(self_node)
+ except Exception, e:
+ message = "Error executing extension element '%s': %s" % (
+ c_inst_node.name, e)
+ xslt.xsltTransformError(c_ctxt, NULL, c_inst_node, message)
+ context._exc._store_raised()
+ except:
+ # just in case
+ message = "Error executing extension element '%s'" % c_inst_node.name
+ xslt.xsltTransformError(c_ctxt, NULL, c_inst_node, message)
+ context._exc._store_raised()
More information about the lxml-checkins
mailing list