|
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
|