You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

243 lines
11 KiB

  1. # XSLT extension elements
  2. cdef class XSLTExtension:
  3. """Base class of an XSLT extension element.
  4. """
  5. def execute(self, context, self_node, input_node, output_parent):
  6. """execute(self, context, self_node, input_node, output_parent)
  7. Execute this extension element.
  8. Subclasses must override this method. They may append
  9. elements to the `output_parent` element here, or set its text
  10. content. To this end, the `input_node` provides read-only
  11. access to the current node in the input document, and the
  12. `self_node` points to the extension element in the stylesheet.
  13. Note that the `output_parent` parameter may be `None` if there
  14. is no parent element in the current context (e.g. no content
  15. was added to the output tree yet).
  16. """
  17. pass
  18. def apply_templates(self, _XSLTContext context not None, node, output_parent=None,
  19. *, elements_only=False, remove_blank_text=False):
  20. """apply_templates(self, context, node, output_parent=None, elements_only=False, remove_blank_text=False)
  21. Call this method to retrieve the result of applying templates
  22. to an element.
  23. The return value is a list of elements or text strings that
  24. were generated by the XSLT processor. If you pass
  25. ``elements_only=True``, strings will be discarded from the result
  26. list. The option ``remove_blank_text=True`` will only discard
  27. strings that consist entirely of whitespace (e.g. formatting).
  28. These options do not apply to Elements, only to bare string results.
  29. If you pass an Element as `output_parent` parameter, the result
  30. will instead be appended to the element (including attributes
  31. etc.) and the return value will be `None`. This is a safe way
  32. to generate content into the output document directly, without
  33. having to take care of special values like text or attributes.
  34. Note that the string discarding options will be ignored in this
  35. case.
  36. """
  37. cdef xmlNode* c_parent
  38. cdef xmlNode* c_node
  39. cdef xmlNode* c_context_node
  40. assert context._xsltCtxt is not NULL, "XSLT context not initialised"
  41. c_context_node = _roNodeOf(node)
  42. #assert c_context_node.doc is context._xsltContext.node.doc, \
  43. # "switching input documents during transformation is not currently supported"
  44. if output_parent is not None:
  45. c_parent = _nonRoNodeOf(output_parent)
  46. else:
  47. c_parent = tree.xmlNewDocNode(
  48. context._xsltCtxt.output, NULL, <unsigned char*>"fake-parent", NULL)
  49. c_node = context._xsltCtxt.insert
  50. context._xsltCtxt.insert = c_parent
  51. xslt.xsltProcessOneNode(
  52. context._xsltCtxt, c_context_node, NULL)
  53. context._xsltCtxt.insert = c_node
  54. if output_parent is not None:
  55. return None
  56. try:
  57. return self._collectXSLTResultContent(
  58. context, c_parent, elements_only, remove_blank_text)
  59. finally:
  60. # free all intermediate nodes that will not be freed by proxies
  61. tree.xmlFreeNode(c_parent)
  62. def process_children(self, _XSLTContext context not None, output_parent=None,
  63. *, elements_only=False, remove_blank_text=False):
  64. """process_children(self, context, output_parent=None, elements_only=False, remove_blank_text=False)
  65. Call this method to process the XSLT content of the extension
  66. element itself.
  67. The return value is a list of elements or text strings that
  68. were generated by the XSLT processor. If you pass
  69. ``elements_only=True``, strings will be discarded from the result
  70. list. The option ``remove_blank_text=True`` will only discard
  71. strings that consist entirely of whitespace (e.g. formatting).
  72. These options do not apply to Elements, only to bare string results.
  73. If you pass an Element as `output_parent` parameter, the result
  74. will instead be appended to the element (including attributes
  75. etc.) and the return value will be `None`. This is a safe way
  76. to generate content into the output document directly, without
  77. having to take care of special values like text or attributes.
  78. Note that the string discarding options will be ignored in this
  79. case.
  80. """
  81. cdef xmlNode* c_parent
  82. cdef xslt.xsltTransformContext* c_ctxt = context._xsltCtxt
  83. cdef xmlNode* c_old_output_parent = c_ctxt.insert
  84. assert context._xsltCtxt is not NULL, "XSLT context not initialised"
  85. # output_parent node is used for adding results instead of
  86. # elements list used in apply_templates, that's easier and allows to
  87. # use attributes added to extension element with <xsl:attribute>.
  88. if output_parent is not None:
  89. c_parent = _nonRoNodeOf(output_parent)
  90. else:
  91. c_parent = tree.xmlNewDocNode(
  92. context._xsltCtxt.output, NULL, <unsigned char*>"fake-parent", NULL)
  93. c_ctxt.insert = c_parent
  94. xslt.xsltApplyOneTemplate(c_ctxt,
  95. c_ctxt.node, c_ctxt.inst.children, NULL, NULL)
  96. c_ctxt.insert = c_old_output_parent
  97. if output_parent is not None:
  98. return None
  99. try:
  100. return self._collectXSLTResultContent(
  101. context, c_parent, elements_only, remove_blank_text)
  102. finally:
  103. # free all intermediate nodes that will not be freed by proxies
  104. tree.xmlFreeNode(c_parent)
  105. cdef _collectXSLTResultContent(self, _XSLTContext context, xmlNode* c_parent,
  106. bint elements_only, bint remove_blank_text):
  107. cdef xmlNode* c_node
  108. cdef xmlNode* c_next
  109. cdef _ReadOnlyProxy proxy
  110. cdef list results = [] # or maybe _collectAttributes(c_parent, 2) ?
  111. c_node = c_parent.children
  112. while c_node is not NULL:
  113. c_next = c_node.next
  114. if c_node.type == tree.XML_TEXT_NODE:
  115. if not elements_only:
  116. s = funicode(c_node.content)
  117. if not remove_blank_text or s.strip():
  118. results.append(s)
  119. s = None
  120. elif c_node.type == tree.XML_ELEMENT_NODE:
  121. proxy = _newReadOnlyProxy(
  122. context._extension_element_proxy, c_node)
  123. results.append(proxy)
  124. # unlink node and make sure it will be freed later on
  125. tree.xmlUnlinkNode(c_node)
  126. proxy.free_after_use()
  127. else:
  128. raise TypeError, \
  129. f"unsupported XSLT result type: {c_node.type}"
  130. c_node = c_next
  131. return results
  132. cdef _registerXSLTExtensions(xslt.xsltTransformContext* c_ctxt,
  133. extension_dict):
  134. for ns_utf, name_utf in extension_dict:
  135. xslt.xsltRegisterExtElement(
  136. c_ctxt, _xcstr(name_utf), _xcstr(ns_utf),
  137. <xslt.xsltTransformFunction>_callExtensionElement)
  138. cdef void _callExtensionElement(xslt.xsltTransformContext* c_ctxt,
  139. xmlNode* c_context_node,
  140. xmlNode* c_inst_node,
  141. void* dummy) noexcept with gil:
  142. cdef _XSLTContext context
  143. cdef XSLTExtension extension
  144. cdef python.PyObject* dict_result
  145. cdef xmlNode* c_node
  146. cdef _ReadOnlyProxy context_node = None, self_node = None
  147. cdef object output_parent # not restricted to ro-nodes
  148. c_uri = _getNs(c_inst_node)
  149. if c_uri is NULL:
  150. # not allowed, and should never happen
  151. return
  152. if c_ctxt.xpathCtxt.userData is NULL:
  153. # just for safety, should never happen
  154. return
  155. context = <_XSLTContext>c_ctxt.xpathCtxt.userData
  156. try:
  157. try:
  158. dict_result = python.PyDict_GetItem(
  159. context._extension_elements, (c_uri, c_inst_node.name))
  160. if dict_result is NULL:
  161. raise KeyError, f"extension element {funicode(c_inst_node.name)} not found"
  162. extension = <object>dict_result
  163. try:
  164. # build the context proxy nodes
  165. self_node = _newReadOnlyProxy(None, c_inst_node)
  166. if _isElement(c_ctxt.insert):
  167. output_parent = _newAppendOnlyProxy(self_node, c_ctxt.insert)
  168. else:
  169. # may be the document node or other stuff
  170. output_parent = _newOpaqueAppendOnlyNodeWrapper(c_ctxt.insert)
  171. if c_context_node.type in (tree.XML_DOCUMENT_NODE,
  172. tree.XML_HTML_DOCUMENT_NODE):
  173. c_node = tree.xmlDocGetRootElement(<xmlDoc*>c_context_node)
  174. if c_node is not NULL:
  175. context_node = _newReadOnlyProxy(self_node, c_node)
  176. else:
  177. context_node = None
  178. elif c_context_node.type in (tree.XML_ATTRIBUTE_NODE,
  179. tree.XML_TEXT_NODE,
  180. tree.XML_CDATA_SECTION_NODE):
  181. # this isn't easy to support using read-only
  182. # nodes, as the smart-string factory must
  183. # instantiate the parent proxy somehow...
  184. raise TypeError(f"Unsupported element type: {c_context_node.type}")
  185. else:
  186. context_node = _newReadOnlyProxy(self_node, c_context_node)
  187. # run the XSLT extension
  188. context._extension_element_proxy = self_node
  189. extension.execute(context, self_node, context_node, output_parent)
  190. finally:
  191. context._extension_element_proxy = None
  192. if self_node is not None:
  193. _freeReadOnlyProxies(self_node)
  194. except Exception as e:
  195. try:
  196. e = unicode(e).encode("UTF-8")
  197. except:
  198. e = repr(e).encode("UTF-8")
  199. message = python.PyBytes_FromFormat(
  200. "Error executing extension element '%s': %s",
  201. c_inst_node.name, _cstr(e))
  202. xslt.xsltTransformError(c_ctxt, NULL, c_inst_node, "%s", message)
  203. context._exc._store_raised()
  204. except:
  205. # just in case
  206. message = python.PyBytes_FromFormat(
  207. "Error executing extension element '%s'", c_inst_node.name)
  208. xslt.xsltTransformError(c_ctxt, NULL, c_inst_node, "%s", message)
  209. context._exc._store_raised()
  210. except:
  211. # no Python functions here - everything can fail...
  212. xslt.xsltTransformError(c_ctxt, NULL, c_inst_node,
  213. "Error during XSLT extension element evaluation")
  214. context._exc._store_raised()
  215. finally:
  216. return # swallow any further exceptions