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