# XSLT extension elements

cdef class XSLTExtension:
    u"""Base class of an XSLT extension element.
    """
    def execute(self, context, self_node, input_node, output_parent):
        u"""execute(self, context, self_node, input_node, output_parent)
        Execute this extension element.

        Subclasses must override this method.  They 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):
        u"""apply_templates(self, context, node)

        Call this method to retrieve the result of applying templates
        to an element.

        The return value is a list of elements or text strings that
        were generated by the XSLT processor.
        """
        cdef xmlNode* c_parent
        cdef xmlNode* c_node
        cdef xmlNode* c_next
        cdef xmlNode* c_context_node
        cdef _ReadOnlyElementProxy proxy
        cdef list results
        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 = [] # or maybe _collectAttributes(c_parent, 2) ?
        c_node = c_parent.children
        try:
            while c_node is not NULL:
                c_next = c_node.next
                if c_node.type == tree.XML_TEXT_NODE:
                    results.append(funicode(c_node.content))
                elif c_node.type == tree.XML_ELEMENT_NODE:
                    proxy = _newReadOnlyProxy(
                        context._extension_element_proxy, c_node)
                    results.append(proxy)
                    # unlink node and make sure it will be freed later on
                    tree.xmlUnlinkNode(c_node)
                    proxy.free_after_use()
                else:
                    raise TypeError, \
                        u"unsupported XSLT result type: %d" % c_node.type
                c_node = c_next
        finally:
            # free all intermediate nodes that will not be freed by proxies
            tree.xmlFreeNode(c_parent)
        return results


cdef _registerXSLTExtensions(xslt.xsltTransformContext* c_ctxt,
                             extension_dict):
    for ns_utf, name_utf in extension_dict:
        xslt.xsltRegisterExtElement(
            c_ctxt, _cstr(name_utf), _cstr(ns_utf), _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 xmlNode* c_node
    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, \
                u"extension element %s not found" % funicode(c_inst_node.name)
        extension = <object>dict_result

        try:
            self_node     = _newReadOnlyProxy(None, c_inst_node)
            output_parent = _newAppendOnlyProxy(self_node, c_ctxt.insert)

            if c_context_node.type == tree.XML_ELEMENT_NODE:
                context_node  = _newReadOnlyProxy(self_node, c_context_node)
            elif c_context_node.type in (tree.XML_DOCUMENT_NODE,
                                         tree.XML_HTML_DOCUMENT_NODE):
                c_node = tree.xmlDocGetRootElement(<xmlDoc*>c_context_node)
                if c_node is not NULL:
                    context_node = _newReadOnlyProxy(self_node, c_node)
                else:
                    context_node = None
            else:
                raise TypeError, \
                      u"Unsupported XSLT context node type %d, extension elements require an element" % c_context_node.type

            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:
        e = unicode(e).encode(u"UTF-8")
        message = python.PyString_FromFormat(
            "Error executing extension element '%s': %s",
            c_inst_node.name, _cstr(e))
        xslt.xsltTransformError(c_ctxt, NULL, c_inst_node, message)
        context._exc._store_raised()
    except:
        # just in case
        message = python.PyString_FromFormat(
            "Error executing extension element '%s'", c_inst_node.name)
        xslt.xsltTransformError(c_ctxt, NULL, c_inst_node, message)
        context._exc._store_raised()
