[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