Blame src/lxml/xsltext.pxi

Packit Service b74dd5
# XSLT extension elements
Packit Service b74dd5
Packit Service b74dd5
cdef class XSLTExtension:
Packit Service b74dd5
    u"""Base class of an XSLT extension element.
Packit Service b74dd5
    """
Packit Service b74dd5
    def execute(self, context, self_node, input_node, output_parent):
Packit Service b74dd5
        u"""execute(self, context, self_node, input_node, output_parent)
Packit Service b74dd5
        Execute this extension element.
Packit Service b74dd5
Packit Service b74dd5
        Subclasses must override this method.  They may append
Packit Service b74dd5
        elements to the `output_parent` element here, or set its text
Packit Service b74dd5
        content.  To this end, the `input_node` provides read-only
Packit Service b74dd5
        access to the current node in the input document, and the
Packit Service b74dd5
        `self_node` points to the extension element in the stylesheet.
Packit Service b74dd5
Packit Service b74dd5
        Note that the `output_parent` parameter may be `None` if there
Packit Service b74dd5
        is no parent element in the current context (e.g. no content
Packit Service b74dd5
        was added to the output tree yet).
Packit Service b74dd5
        """
Packit Service b74dd5
        pass
Packit Service b74dd5
Packit Service b74dd5
    def apply_templates(self, _XSLTContext context not None, node, output_parent=None,
Packit Service b74dd5
                        *, elements_only=False, remove_blank_text=False):
Packit Service b74dd5
        u"""apply_templates(self, context, node, output_parent=None, elements_only=False, remove_blank_text=False)
Packit Service b74dd5
Packit Service b74dd5
        Call this method to retrieve the result of applying templates
Packit Service b74dd5
        to an element.
Packit Service b74dd5
Packit Service b74dd5
        The return value is a list of elements or text strings that
Packit Service b74dd5
        were generated by the XSLT processor.  If you pass
Packit Service b74dd5
        ``elements_only=True``, strings will be discarded from the result
Packit Service b74dd5
        list.  The option ``remove_blank_text=True`` will only discard
Packit Service b74dd5
        strings that consist entirely of whitespace (e.g. formatting).
Packit Service b74dd5
        These options do not apply to Elements, only to bare string results.
Packit Service b74dd5
Packit Service b74dd5
        If you pass an Element as `output_parent` parameter, the result
Packit Service b74dd5
        will instead be appended to the element (including attributes
Packit Service b74dd5
        etc.) and the return value will be `None`.  This is a safe way
Packit Service b74dd5
        to generate content into the output document directly, without
Packit Service b74dd5
        having to take care of special values like text or attributes.
Packit Service b74dd5
        Note that the string discarding options will be ignored in this
Packit Service b74dd5
        case.
Packit Service b74dd5
        """
Packit Service b74dd5
        cdef xmlNode* c_parent
Packit Service b74dd5
        cdef xmlNode* c_node
Packit Service b74dd5
        cdef xmlNode* c_context_node
Packit Service b74dd5
        assert context._xsltCtxt is not NULL, "XSLT context not initialised"
Packit Service b74dd5
        c_context_node = _roNodeOf(node)
Packit Service b74dd5
        #assert c_context_node.doc is context._xsltContext.node.doc, \
Packit Service b74dd5
        #    "switching input documents during transformation is not currently supported"
Packit Service b74dd5
Packit Service b74dd5
        if output_parent is not None:
Packit Service b74dd5
            c_parent = _nonRoNodeOf(output_parent)
Packit Service b74dd5
        else:
Packit Service b74dd5
            c_parent = tree.xmlNewDocNode(
Packit Service b74dd5
                context._xsltCtxt.output, NULL, <unsigned char*>"fake-parent", NULL)
Packit Service b74dd5
Packit Service b74dd5
        c_node = context._xsltCtxt.insert
Packit Service b74dd5
        context._xsltCtxt.insert = c_parent
Packit Service b74dd5
        xslt.xsltProcessOneNode(
Packit Service b74dd5
            context._xsltCtxt, c_context_node, NULL)
Packit Service b74dd5
        context._xsltCtxt.insert = c_node
Packit Service b74dd5
Packit Service b74dd5
        if output_parent is not None:
Packit Service b74dd5
            return None
Packit Service b74dd5
Packit Service b74dd5
        try:
Packit Service b74dd5
            return self._collectXSLTResultContent(
Packit Service b74dd5
                context, c_parent, elements_only, remove_blank_text)
Packit Service b74dd5
        finally:
Packit Service b74dd5
            # free all intermediate nodes that will not be freed by proxies
Packit Service b74dd5
            tree.xmlFreeNode(c_parent)
Packit Service b74dd5
Packit Service b74dd5
    def process_children(self, _XSLTContext context not None, output_parent=None,
Packit Service b74dd5
                         *, elements_only=False, remove_blank_text=False):
Packit Service b74dd5
        u"""process_children(self, context, output_parent=None, elements_only=False, remove_blank_text=False)
Packit Service b74dd5
Packit Service b74dd5
        Call this method to process the XSLT content of the extension
Packit Service b74dd5
        element itself.
Packit Service b74dd5
Packit Service b74dd5
        The return value is a list of elements or text strings that
Packit Service b74dd5
        were generated by the XSLT processor.  If you pass
Packit Service b74dd5
        ``elements_only=True``, strings will be discarded from the result
Packit Service b74dd5
        list.  The option ``remove_blank_text=True`` will only discard
Packit Service b74dd5
        strings that consist entirely of whitespace (e.g. formatting).
Packit Service b74dd5
        These options do not apply to Elements, only to bare string results.
Packit Service b74dd5
Packit Service b74dd5
        If you pass an Element as `output_parent` parameter, the result
Packit Service b74dd5
        will instead be appended to the element (including attributes
Packit Service b74dd5
        etc.) and the return value will be `None`.  This is a safe way
Packit Service b74dd5
        to generate content into the output document directly, without
Packit Service b74dd5
        having to take care of special values like text or attributes.
Packit Service b74dd5
        Note that the string discarding options will be ignored in this
Packit Service b74dd5
        case.
Packit Service b74dd5
        """
Packit Service b74dd5
        cdef xmlNode* c_parent
Packit Service b74dd5
        cdef xslt.xsltTransformContext* c_ctxt = context._xsltCtxt
Packit Service b74dd5
        cdef xmlNode* c_old_output_parent = c_ctxt.insert
Packit Service b74dd5
        assert context._xsltCtxt is not NULL, "XSLT context not initialised"
Packit Service b74dd5
Packit Service b74dd5
        # output_parent node is used for adding results instead of
Packit Service b74dd5
        # elements list used in apply_templates, that's easier and allows to
Packit Service b74dd5
        # use attributes added to extension element with <xsl:attribute>.
Packit Service b74dd5
Packit Service b74dd5
        if output_parent is not None:
Packit Service b74dd5
            c_parent = _nonRoNodeOf(output_parent)
Packit Service b74dd5
        else:
Packit Service b74dd5
            c_parent = tree.xmlNewDocNode(
Packit Service b74dd5
                context._xsltCtxt.output, NULL, <unsigned char*>"fake-parent", NULL)
Packit Service b74dd5
Packit Service b74dd5
        c_ctxt.insert = c_parent
Packit Service b74dd5
        xslt.xsltApplyOneTemplate(c_ctxt,
Packit Service b74dd5
            c_ctxt.node, c_ctxt.inst.children, NULL, NULL)
Packit Service b74dd5
        c_ctxt.insert = c_old_output_parent
Packit Service b74dd5
Packit Service b74dd5
        if output_parent is not None:
Packit Service b74dd5
            return None
Packit Service b74dd5
Packit Service b74dd5
        try:
Packit Service b74dd5
            return self._collectXSLTResultContent(
Packit Service b74dd5
                context, c_parent, elements_only, remove_blank_text)
Packit Service b74dd5
        finally:
Packit Service b74dd5
            # free all intermediate nodes that will not be freed by proxies
Packit Service b74dd5
            tree.xmlFreeNode(c_parent)
Packit Service b74dd5
Packit Service b74dd5
    cdef _collectXSLTResultContent(self, _XSLTContext context, xmlNode* c_parent,
Packit Service b74dd5
                                   bint elements_only, bint remove_blank_text):
Packit Service b74dd5
        cdef xmlNode* c_node
Packit Service b74dd5
        cdef xmlNode* c_next
Packit Service b74dd5
        cdef _ReadOnlyProxy proxy
Packit Service b74dd5
        cdef list results = [] # or maybe _collectAttributes(c_parent, 2) ?
Packit Service b74dd5
        c_node = c_parent.children
Packit Service b74dd5
        while c_node is not NULL:
Packit Service b74dd5
            c_next = c_node.next
Packit Service b74dd5
            if c_node.type == tree.XML_TEXT_NODE:
Packit Service b74dd5
                if not elements_only:
Packit Service b74dd5
                    s = funicode(c_node.content)
Packit Service b74dd5
                    if not remove_blank_text or s.strip():
Packit Service b74dd5
                        results.append(s)
Packit Service b74dd5
                    s = None
Packit Service b74dd5
            elif c_node.type == tree.XML_ELEMENT_NODE:
Packit Service b74dd5
                proxy = _newReadOnlyProxy(
Packit Service b74dd5
                    context._extension_element_proxy, c_node)
Packit Service b74dd5
                results.append(proxy)
Packit Service b74dd5
                # unlink node and make sure it will be freed later on
Packit Service b74dd5
                tree.xmlUnlinkNode(c_node)
Packit Service b74dd5
                proxy.free_after_use()
Packit Service b74dd5
            else:
Packit Service b74dd5
                raise TypeError, \
Packit Service b74dd5
                    f"unsupported XSLT result type: {c_node.type}"
Packit Service b74dd5
            c_node = c_next
Packit Service b74dd5
        return results
Packit Service b74dd5
Packit Service b74dd5
Packit Service b74dd5
cdef _registerXSLTExtensions(xslt.xsltTransformContext* c_ctxt,
Packit Service b74dd5
                             extension_dict):
Packit Service b74dd5
    for ns_utf, name_utf in extension_dict:
Packit Service b74dd5
        xslt.xsltRegisterExtElement(
Packit Service b74dd5
            c_ctxt, _xcstr(name_utf), _xcstr(ns_utf),
Packit Service b74dd5
            <xslt.xsltTransformFunction>_callExtensionElement)
Packit Service b74dd5
Packit Service b74dd5
cdef void _callExtensionElement(xslt.xsltTransformContext* c_ctxt,
Packit Service b74dd5
                                xmlNode* c_context_node,
Packit Service b74dd5
                                xmlNode* c_inst_node,
Packit Service b74dd5
                                void* dummy) with gil:
Packit Service b74dd5
    cdef _XSLTContext context
Packit Service b74dd5
    cdef XSLTExtension extension
Packit Service b74dd5
    cdef python.PyObject* dict_result
Packit Service b74dd5
    cdef xmlNode* c_node
Packit Service b74dd5
    cdef _ReadOnlyProxy context_node = None, self_node = None
Packit Service b74dd5
    cdef object output_parent # not restricted to ro-nodes
Packit Service b74dd5
    c_uri = _getNs(c_inst_node)
Packit Service b74dd5
    if c_uri is NULL:
Packit Service b74dd5
        # not allowed, and should never happen
Packit Service b74dd5
        return
Packit Service b74dd5
    if c_ctxt.xpathCtxt.userData is NULL:
Packit Service b74dd5
        # just for safety, should never happen
Packit Service b74dd5
        return
Packit Service b74dd5
    context = <_XSLTContext>c_ctxt.xpathCtxt.userData
Packit Service b74dd5
    try:
Packit Service b74dd5
        try:
Packit Service b74dd5
            dict_result = python.PyDict_GetItem(
Packit Service b74dd5
                context._extension_elements, (c_uri, c_inst_node.name))
Packit Service b74dd5
            if dict_result is NULL:
Packit Service b74dd5
                raise KeyError, f"extension element {funicode(c_inst_node.name)} not found"
Packit Service b74dd5
            extension = <object>dict_result
Packit Service b74dd5
Packit Service b74dd5
            try:
Packit Service b74dd5
                # build the context proxy nodes
Packit Service b74dd5
                self_node = _newReadOnlyProxy(None, c_inst_node)
Packit Service b74dd5
                if _isElement(c_ctxt.insert):
Packit Service b74dd5
                    output_parent = _newAppendOnlyProxy(self_node, c_ctxt.insert)
Packit Service b74dd5
                else:
Packit Service b74dd5
                    # may be the document node or other stuff
Packit Service b74dd5
                    output_parent = _newOpaqueAppendOnlyNodeWrapper(c_ctxt.insert)
Packit Service b74dd5
                if c_context_node.type in (tree.XML_DOCUMENT_NODE,
Packit Service b74dd5
                                           tree.XML_HTML_DOCUMENT_NODE):
Packit Service b74dd5
                    c_node = tree.xmlDocGetRootElement(<xmlDoc*>c_context_node)
Packit Service b74dd5
                    if c_node is not NULL:
Packit Service b74dd5
                        context_node = _newReadOnlyProxy(self_node, c_node)
Packit Service b74dd5
                    else:
Packit Service b74dd5
                        context_node = None
Packit Service b74dd5
                elif c_context_node.type in (tree.XML_ATTRIBUTE_NODE,
Packit Service b74dd5
                                             tree.XML_TEXT_NODE,
Packit Service b74dd5
                                             tree.XML_CDATA_SECTION_NODE):
Packit Service b74dd5
                    # this isn't easy to support using read-only
Packit Service b74dd5
                    # nodes, as the smart-string factory must
Packit Service b74dd5
                    # instantiate the parent proxy somehow...
Packit Service b74dd5
                    raise TypeError(f"Unsupported element type: {c_context_node.type}")
Packit Service b74dd5
                else:
Packit Service b74dd5
                    context_node  = _newReadOnlyProxy(self_node, c_context_node)
Packit Service b74dd5
Packit Service b74dd5
                # run the XSLT extension
Packit Service b74dd5
                context._extension_element_proxy = self_node
Packit Service b74dd5
                extension.execute(context, self_node, context_node, output_parent)
Packit Service b74dd5
            finally:
Packit Service b74dd5
                context._extension_element_proxy = None
Packit Service b74dd5
                if self_node is not None:
Packit Service b74dd5
                    _freeReadOnlyProxies(self_node)
Packit Service b74dd5
        except Exception as e:
Packit Service b74dd5
            try:
Packit Service b74dd5
                e = unicode(e).encode(u"UTF-8")
Packit Service b74dd5
            except:
Packit Service b74dd5
                e = repr(e).encode(u"UTF-8")
Packit Service b74dd5
            message = python.PyBytes_FromFormat(
Packit Service b74dd5
                "Error executing extension element '%s': %s",
Packit Service b74dd5
                c_inst_node.name, _cstr(e))
Packit Service b74dd5
            xslt.xsltTransformError(c_ctxt, NULL, c_inst_node, "%s", message)
Packit Service b74dd5
            context._exc._store_raised()
Packit Service b74dd5
        except:
Packit Service b74dd5
            # just in case
Packit Service b74dd5
            message = python.PyBytes_FromFormat(
Packit Service b74dd5
                "Error executing extension element '%s'", c_inst_node.name)
Packit Service b74dd5
            xslt.xsltTransformError(c_ctxt, NULL, c_inst_node, "%s", message)
Packit Service b74dd5
            context._exc._store_raised()
Packit Service b74dd5
    except:
Packit Service b74dd5
        # no Python functions here - everything can fail...
Packit Service b74dd5
        xslt.xsltTransformError(c_ctxt, NULL, c_inst_node,
Packit Service b74dd5
                                "Error during XSLT extension element evaluation")
Packit Service b74dd5
        context._exc._store_raised()
Packit Service b74dd5
    finally:
Packit Service b74dd5
        return  # swallow any further exceptions