lxml 6.0.0__cp311-cp311-win_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. lxml/ElementInclude.py +244 -0
  2. lxml/__init__.py +22 -0
  3. lxml/_elementpath.cp311-win_arm64.pyd +0 -0
  4. lxml/_elementpath.py +343 -0
  5. lxml/apihelpers.pxi +1801 -0
  6. lxml/builder.cp311-win_arm64.pyd +0 -0
  7. lxml/builder.py +243 -0
  8. lxml/classlookup.pxi +580 -0
  9. lxml/cleanup.pxi +215 -0
  10. lxml/cssselect.py +101 -0
  11. lxml/debug.pxi +36 -0
  12. lxml/docloader.pxi +178 -0
  13. lxml/doctestcompare.py +488 -0
  14. lxml/dtd.pxi +479 -0
  15. lxml/etree.cp311-win_arm64.pyd +0 -0
  16. lxml/etree.h +244 -0
  17. lxml/etree.pyx +3853 -0
  18. lxml/etree_api.h +204 -0
  19. lxml/extensions.pxi +830 -0
  20. lxml/html/ElementSoup.py +10 -0
  21. lxml/html/__init__.py +1927 -0
  22. lxml/html/_diffcommand.py +86 -0
  23. lxml/html/_difflib.cp311-win_arm64.pyd +0 -0
  24. lxml/html/_difflib.py +2106 -0
  25. lxml/html/_html5builder.py +100 -0
  26. lxml/html/_setmixin.py +56 -0
  27. lxml/html/builder.py +173 -0
  28. lxml/html/clean.py +21 -0
  29. lxml/html/defs.py +135 -0
  30. lxml/html/diff.cp311-win_arm64.pyd +0 -0
  31. lxml/html/diff.py +972 -0
  32. lxml/html/formfill.py +299 -0
  33. lxml/html/html5parser.py +260 -0
  34. lxml/html/soupparser.py +314 -0
  35. lxml/html/usedoctest.py +13 -0
  36. lxml/includes/__init__.pxd +0 -0
  37. lxml/includes/__init__.py +0 -0
  38. lxml/includes/c14n.pxd +25 -0
  39. lxml/includes/config.pxd +3 -0
  40. lxml/includes/dtdvalid.pxd +18 -0
  41. lxml/includes/etree_defs.h +379 -0
  42. lxml/includes/etreepublic.pxd +237 -0
  43. lxml/includes/extlibs/__init__.py +0 -0
  44. lxml/includes/extlibs/zconf.h +543 -0
  45. lxml/includes/extlibs/zlib.h +1938 -0
  46. lxml/includes/htmlparser.pxd +56 -0
  47. lxml/includes/libexslt/__init__.py +0 -0
  48. lxml/includes/libexslt/exslt.h +108 -0
  49. lxml/includes/libexslt/exsltconfig.h +70 -0
  50. lxml/includes/libexslt/exsltexports.h +63 -0
  51. lxml/includes/libexslt/libexslt.h +29 -0
  52. lxml/includes/libxml/HTMLparser.h +320 -0
  53. lxml/includes/libxml/HTMLtree.h +147 -0
  54. lxml/includes/libxml/SAX.h +204 -0
  55. lxml/includes/libxml/SAX2.h +173 -0
  56. lxml/includes/libxml/__init__.py +0 -0
  57. lxml/includes/libxml/c14n.h +128 -0
  58. lxml/includes/libxml/catalog.h +182 -0
  59. lxml/includes/libxml/chvalid.h +230 -0
  60. lxml/includes/libxml/debugXML.h +217 -0
  61. lxml/includes/libxml/dict.h +81 -0
  62. lxml/includes/libxml/encoding.h +233 -0
  63. lxml/includes/libxml/entities.h +151 -0
  64. lxml/includes/libxml/globals.h +529 -0
  65. lxml/includes/libxml/hash.h +236 -0
  66. lxml/includes/libxml/list.h +137 -0
  67. lxml/includes/libxml/nanoftp.h +186 -0
  68. lxml/includes/libxml/nanohttp.h +81 -0
  69. lxml/includes/libxml/parser.h +1265 -0
  70. lxml/includes/libxml/parserInternals.h +662 -0
  71. lxml/includes/libxml/pattern.h +100 -0
  72. lxml/includes/libxml/relaxng.h +218 -0
  73. lxml/includes/libxml/schemasInternals.h +958 -0
  74. lxml/includes/libxml/schematron.h +142 -0
  75. lxml/includes/libxml/threads.h +94 -0
  76. lxml/includes/libxml/tree.h +1314 -0
  77. lxml/includes/libxml/uri.h +94 -0
  78. lxml/includes/libxml/valid.h +448 -0
  79. lxml/includes/libxml/xinclude.h +129 -0
  80. lxml/includes/libxml/xlink.h +189 -0
  81. lxml/includes/libxml/xmlIO.h +369 -0
  82. lxml/includes/libxml/xmlautomata.h +146 -0
  83. lxml/includes/libxml/xmlerror.h +919 -0
  84. lxml/includes/libxml/xmlexports.h +50 -0
  85. lxml/includes/libxml/xmlmemory.h +228 -0
  86. lxml/includes/libxml/xmlmodule.h +57 -0
  87. lxml/includes/libxml/xmlreader.h +428 -0
  88. lxml/includes/libxml/xmlregexp.h +222 -0
  89. lxml/includes/libxml/xmlsave.h +88 -0
  90. lxml/includes/libxml/xmlschemas.h +246 -0
  91. lxml/includes/libxml/xmlschemastypes.h +152 -0
  92. lxml/includes/libxml/xmlstring.h +140 -0
  93. lxml/includes/libxml/xmlunicode.h +202 -0
  94. lxml/includes/libxml/xmlversion.h +526 -0
  95. lxml/includes/libxml/xmlwriter.h +488 -0
  96. lxml/includes/libxml/xpath.h +575 -0
  97. lxml/includes/libxml/xpathInternals.h +632 -0
  98. lxml/includes/libxml/xpointer.h +137 -0
  99. lxml/includes/libxslt/__init__.py +0 -0
  100. lxml/includes/libxslt/attributes.h +39 -0
  101. lxml/includes/libxslt/documents.h +93 -0
  102. lxml/includes/libxslt/extensions.h +262 -0
  103. lxml/includes/libxslt/extra.h +72 -0
  104. lxml/includes/libxslt/functions.h +78 -0
  105. lxml/includes/libxslt/imports.h +75 -0
  106. lxml/includes/libxslt/keys.h +53 -0
  107. lxml/includes/libxslt/libxslt.h +36 -0
  108. lxml/includes/libxslt/namespaces.h +68 -0
  109. lxml/includes/libxslt/numbersInternals.h +73 -0
  110. lxml/includes/libxslt/preproc.h +43 -0
  111. lxml/includes/libxslt/security.h +104 -0
  112. lxml/includes/libxslt/templates.h +77 -0
  113. lxml/includes/libxslt/transform.h +207 -0
  114. lxml/includes/libxslt/trio.h +216 -0
  115. lxml/includes/libxslt/triodef.h +220 -0
  116. lxml/includes/libxslt/variables.h +118 -0
  117. lxml/includes/libxslt/win32config.h +51 -0
  118. lxml/includes/libxslt/xslt.h +110 -0
  119. lxml/includes/libxslt/xsltInternals.h +1992 -0
  120. lxml/includes/libxslt/xsltconfig.h +179 -0
  121. lxml/includes/libxslt/xsltexports.h +64 -0
  122. lxml/includes/libxslt/xsltlocale.h +44 -0
  123. lxml/includes/libxslt/xsltutils.h +343 -0
  124. lxml/includes/lxml-version.h +3 -0
  125. lxml/includes/relaxng.pxd +64 -0
  126. lxml/includes/schematron.pxd +34 -0
  127. lxml/includes/tree.pxd +492 -0
  128. lxml/includes/uri.pxd +5 -0
  129. lxml/includes/xinclude.pxd +22 -0
  130. lxml/includes/xmlerror.pxd +852 -0
  131. lxml/includes/xmlparser.pxd +303 -0
  132. lxml/includes/xmlschema.pxd +35 -0
  133. lxml/includes/xpath.pxd +136 -0
  134. lxml/includes/xslt.pxd +190 -0
  135. lxml/isoschematron/__init__.py +348 -0
  136. lxml/isoschematron/resources/rng/iso-schematron.rng +709 -0
  137. lxml/isoschematron/resources/xsl/RNG2Schtrn.xsl +75 -0
  138. lxml/isoschematron/resources/xsl/XSD2Schtrn.xsl +77 -0
  139. lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_abstract_expand.xsl +313 -0
  140. lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_dsdl_include.xsl +1160 -0
  141. lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_schematron_message.xsl +55 -0
  142. lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_schematron_skeleton_for_xslt1.xsl +1796 -0
  143. lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_svrl_for_xslt1.xsl +588 -0
  144. lxml/isoschematron/resources/xsl/iso-schematron-xslt1/readme.txt +84 -0
  145. lxml/iterparse.pxi +438 -0
  146. lxml/lxml.etree.h +244 -0
  147. lxml/lxml.etree_api.h +204 -0
  148. lxml/nsclasses.pxi +281 -0
  149. lxml/objectify.cp311-win_arm64.pyd +0 -0
  150. lxml/objectify.pyx +2149 -0
  151. lxml/objectpath.pxi +332 -0
  152. lxml/parser.pxi +2059 -0
  153. lxml/parsertarget.pxi +180 -0
  154. lxml/proxy.pxi +619 -0
  155. lxml/public-api.pxi +178 -0
  156. lxml/pyclasslookup.py +3 -0
  157. lxml/readonlytree.pxi +565 -0
  158. lxml/relaxng.pxi +165 -0
  159. lxml/sax.cp311-win_arm64.pyd +0 -0
  160. lxml/sax.py +286 -0
  161. lxml/saxparser.pxi +875 -0
  162. lxml/schematron.pxi +173 -0
  163. lxml/serializer.pxi +1849 -0
  164. lxml/usedoctest.py +13 -0
  165. lxml/xinclude.pxi +67 -0
  166. lxml/xmlerror.pxi +1654 -0
  167. lxml/xmlid.pxi +179 -0
  168. lxml/xmlschema.pxi +215 -0
  169. lxml/xpath.pxi +487 -0
  170. lxml/xslt.pxi +957 -0
  171. lxml/xsltext.pxi +242 -0
  172. lxml-6.0.0.dist-info/METADATA +163 -0
  173. lxml-6.0.0.dist-info/RECORD +177 -0
  174. lxml-6.0.0.dist-info/WHEEL +5 -0
  175. lxml-6.0.0.dist-info/licenses/LICENSE.txt +31 -0
  176. lxml-6.0.0.dist-info/licenses/LICENSES.txt +29 -0
  177. lxml-6.0.0.dist-info/top_level.txt +1 -0
lxml/extensions.pxi ADDED
@@ -0,0 +1,830 @@
1
+ # support for extension functions in XPath and XSLT
2
+
3
+ cdef class XPathError(LxmlError):
4
+ """Base class of all XPath errors.
5
+ """
6
+
7
+ cdef class XPathEvalError(XPathError):
8
+ """Error during XPath evaluation.
9
+ """
10
+
11
+ cdef class XPathFunctionError(XPathEvalError):
12
+ """Internal error looking up an XPath extension function.
13
+ """
14
+
15
+ cdef class XPathResultError(XPathEvalError):
16
+ """Error handling an XPath result.
17
+ """
18
+
19
+
20
+ # forward declarations
21
+
22
+ ctypedef int (*_register_function)(void* ctxt, name_utf, ns_uri_utf)
23
+ cdef class _ExsltRegExp
24
+
25
+ ################################################################################
26
+ # Base class for XSLT and XPath evaluation contexts: functions, namespaces, ...
27
+
28
+ @cython.internal
29
+ cdef class _BaseContext:
30
+ cdef xpath.xmlXPathContext* _xpathCtxt
31
+ cdef _Document _doc
32
+ cdef dict _extensions
33
+ cdef list _namespaces
34
+ cdef list _global_namespaces
35
+ cdef dict _utf_refs
36
+ cdef dict _function_cache
37
+ cdef dict _eval_context_dict
38
+ cdef bint _build_smart_strings
39
+ # for exception handling and temporary reference keeping:
40
+ cdef _TempStore _temp_refs
41
+ cdef set _temp_documents
42
+ cdef _ExceptionContext _exc
43
+ cdef _ErrorLog _error_log
44
+
45
+ def __init__(self, namespaces, extensions, error_log, enable_regexp,
46
+ build_smart_strings):
47
+ cdef _ExsltRegExp _regexp
48
+ cdef dict new_extensions
49
+ cdef list ns
50
+ self._utf_refs = {}
51
+ self._global_namespaces = []
52
+ self._function_cache = {}
53
+ self._eval_context_dict = None
54
+ self._error_log = error_log
55
+
56
+ if extensions is not None:
57
+ # convert extensions to UTF-8
58
+ if isinstance(extensions, dict):
59
+ extensions = (extensions,)
60
+ # format: [ {(ns, name):function} ] -> {(ns_utf, name_utf):function}
61
+ new_extensions = {}
62
+ for extension in extensions:
63
+ for (ns_uri, name), function in extension.items():
64
+ if name is None:
65
+ raise ValueError, "extensions must have non empty names"
66
+ ns_utf = self._to_utf(ns_uri)
67
+ name_utf = self._to_utf(name)
68
+ new_extensions[(ns_utf, name_utf)] = function
69
+ extensions = new_extensions or None
70
+
71
+ if namespaces is not None:
72
+ if isinstance(namespaces, dict):
73
+ namespaces = namespaces.items()
74
+ if namespaces:
75
+ ns = []
76
+ for prefix, ns_uri in namespaces:
77
+ if prefix is None or not prefix:
78
+ raise TypeError, \
79
+ "empty namespace prefix is not supported in XPath"
80
+ if ns_uri is None or not ns_uri:
81
+ raise TypeError, \
82
+ "setting default namespace is not supported in XPath"
83
+ prefix_utf = self._to_utf(prefix)
84
+ ns_uri_utf = self._to_utf(ns_uri)
85
+ ns.append( (prefix_utf, ns_uri_utf) )
86
+ namespaces = ns
87
+ else:
88
+ namespaces = None
89
+
90
+ self._doc = None
91
+ self._exc = _ExceptionContext()
92
+ self._extensions = extensions
93
+ self._namespaces = namespaces
94
+ self._temp_refs = _TempStore()
95
+ self._temp_documents = set()
96
+ self._build_smart_strings = build_smart_strings
97
+
98
+ if enable_regexp:
99
+ _regexp = _ExsltRegExp()
100
+ _regexp._register_in_context(self)
101
+
102
+ cdef _BaseContext _copy(self):
103
+ cdef _BaseContext context
104
+ if self._namespaces is not None:
105
+ namespaces = self._namespaces[:]
106
+ else:
107
+ namespaces = None
108
+ context = self.__class__(namespaces, None, self._error_log, False,
109
+ self._build_smart_strings)
110
+ if self._extensions is not None:
111
+ context._extensions = self._extensions.copy()
112
+ return context
113
+
114
+ cdef bytes _to_utf(self, s):
115
+ "Convert to UTF-8 and keep a reference to the encoded string"
116
+ cdef python.PyObject* dict_result
117
+ if s is None:
118
+ return None
119
+ dict_result = python.PyDict_GetItem(self._utf_refs, s)
120
+ if dict_result is not NULL:
121
+ return <bytes>dict_result
122
+ utf = _utf8(s)
123
+ self._utf_refs[s] = utf
124
+ if python.IS_PYPY:
125
+ # use C level refs, PyPy refs are not enough!
126
+ python.Py_INCREF(utf)
127
+ return utf
128
+
129
+ cdef void _set_xpath_context(self, xpath.xmlXPathContext* xpathCtxt) noexcept:
130
+ self._xpathCtxt = xpathCtxt
131
+ xpathCtxt.userData = <void*>self
132
+ # Need a cast here because older libxml2 releases do not use 'const' in the functype.
133
+ xpathCtxt.error = <xmlerror.xmlStructuredErrorFunc> _receiveXPathError
134
+
135
+ @cython.final
136
+ cdef _register_context(self, _Document doc):
137
+ self._doc = doc
138
+ self._exc.clear()
139
+
140
+ @cython.final
141
+ cdef _cleanup_context(self):
142
+ #xpath.xmlXPathRegisteredNsCleanup(self._xpathCtxt)
143
+ #self.unregisterGlobalNamespaces()
144
+ if python.IS_PYPY:
145
+ # clean up double refs in PyPy (see "_to_utf()" method)
146
+ for ref in self._utf_refs.itervalues():
147
+ python.Py_DECREF(ref)
148
+ self._utf_refs.clear()
149
+ self._eval_context_dict = None
150
+ self._doc = None
151
+
152
+ @cython.final
153
+ cdef _release_context(self):
154
+ if self._xpathCtxt is not NULL:
155
+ self._xpathCtxt.userData = NULL
156
+ self._xpathCtxt = NULL
157
+
158
+ # namespaces (internal UTF-8 methods with leading '_')
159
+
160
+ cdef addNamespace(self, prefix, ns_uri):
161
+ cdef list namespaces
162
+ if prefix is None:
163
+ raise TypeError, "empty prefix is not supported in XPath"
164
+ prefix_utf = self._to_utf(prefix)
165
+ ns_uri_utf = self._to_utf(ns_uri)
166
+ new_item = (prefix_utf, ns_uri_utf)
167
+ if self._namespaces is None:
168
+ self._namespaces = [new_item]
169
+ else:
170
+ namespaces = []
171
+ for item in self._namespaces:
172
+ if item[0] == prefix_utf:
173
+ item = new_item
174
+ new_item = None
175
+ namespaces.append(item)
176
+ if new_item is not None:
177
+ namespaces.append(new_item)
178
+ self._namespaces = namespaces
179
+ if self._xpathCtxt is not NULL:
180
+ xpath.xmlXPathRegisterNs(
181
+ self._xpathCtxt, _xcstr(prefix_utf), _xcstr(ns_uri_utf))
182
+
183
+ cdef registerNamespace(self, prefix, ns_uri):
184
+ if prefix is None:
185
+ raise TypeError, "empty prefix is not supported in XPath"
186
+ prefix_utf = self._to_utf(prefix)
187
+ ns_uri_utf = self._to_utf(ns_uri)
188
+ self._global_namespaces.append(prefix_utf)
189
+ xpath.xmlXPathRegisterNs(self._xpathCtxt,
190
+ _xcstr(prefix_utf), _xcstr(ns_uri_utf))
191
+
192
+ cdef registerLocalNamespaces(self):
193
+ if self._namespaces is None:
194
+ return
195
+ for prefix_utf, ns_uri_utf in self._namespaces:
196
+ xpath.xmlXPathRegisterNs(
197
+ self._xpathCtxt, _xcstr(prefix_utf), _xcstr(ns_uri_utf))
198
+
199
+ cdef registerGlobalNamespaces(self):
200
+ cdef list ns_prefixes = _find_all_extension_prefixes()
201
+ if python.PyList_GET_SIZE(ns_prefixes) > 0:
202
+ for prefix_utf, ns_uri_utf in ns_prefixes:
203
+ self._global_namespaces.append(prefix_utf)
204
+ xpath.xmlXPathRegisterNs(
205
+ self._xpathCtxt, _xcstr(prefix_utf), _xcstr(ns_uri_utf))
206
+
207
+ cdef unregisterGlobalNamespaces(self):
208
+ if python.PyList_GET_SIZE(self._global_namespaces) > 0:
209
+ for prefix_utf in self._global_namespaces:
210
+ xpath.xmlXPathRegisterNs(self._xpathCtxt,
211
+ _xcstr(prefix_utf), NULL)
212
+ del self._global_namespaces[:]
213
+
214
+ cdef void _unregisterNamespace(self, prefix_utf) noexcept:
215
+ xpath.xmlXPathRegisterNs(self._xpathCtxt,
216
+ _xcstr(prefix_utf), NULL)
217
+
218
+ # extension functions
219
+
220
+ cdef int _addLocalExtensionFunction(self, ns_utf, name_utf, function) except -1:
221
+ if self._extensions is None:
222
+ self._extensions = {}
223
+ self._extensions[(ns_utf, name_utf)] = function
224
+ return 0
225
+
226
+ cdef registerGlobalFunctions(self, void* ctxt,
227
+ _register_function reg_func):
228
+ cdef python.PyObject* dict_result
229
+ cdef dict d
230
+ for ns_utf, ns_functions in __FUNCTION_NAMESPACE_REGISTRIES.iteritems():
231
+ dict_result = python.PyDict_GetItem(
232
+ self._function_cache, ns_utf)
233
+ if dict_result is not NULL:
234
+ d = <dict>dict_result
235
+ else:
236
+ d = {}
237
+ self._function_cache[ns_utf] = d
238
+ for name_utf, function in ns_functions.iteritems():
239
+ d[name_utf] = function
240
+ reg_func(ctxt, name_utf, ns_utf)
241
+
242
+ cdef registerLocalFunctions(self, void* ctxt,
243
+ _register_function reg_func):
244
+ cdef python.PyObject* dict_result
245
+ cdef dict d
246
+ if self._extensions is None:
247
+ return # done
248
+ last_ns = None
249
+ d = None
250
+ for (ns_utf, name_utf), function in self._extensions.iteritems():
251
+ if ns_utf is not last_ns or d is None:
252
+ last_ns = ns_utf
253
+ dict_result = python.PyDict_GetItem(
254
+ self._function_cache, ns_utf)
255
+ if dict_result is not NULL:
256
+ d = <dict>dict_result
257
+ else:
258
+ d = {}
259
+ self._function_cache[ns_utf] = d
260
+ d[name_utf] = function
261
+ reg_func(ctxt, name_utf, ns_utf)
262
+
263
+ cdef unregisterAllFunctions(self, void* ctxt,
264
+ _register_function unreg_func):
265
+ for ns_utf, functions in self._function_cache.iteritems():
266
+ for name_utf in functions:
267
+ unreg_func(ctxt, name_utf, ns_utf)
268
+
269
+ cdef unregisterGlobalFunctions(self, void* ctxt,
270
+ _register_function unreg_func):
271
+ for ns_utf, functions in self._function_cache.items():
272
+ for name_utf in functions:
273
+ if self._extensions is None or \
274
+ (ns_utf, name_utf) not in self._extensions:
275
+ unreg_func(ctxt, name_utf, ns_utf)
276
+
277
+ @cython.final
278
+ cdef _find_cached_function(self, const_xmlChar* c_ns_uri, const_xmlChar* c_name):
279
+ """Lookup an extension function in the cache and return it.
280
+
281
+ Parameters: c_ns_uri may be NULL, c_name must not be NULL
282
+ """
283
+ cdef python.PyObject* c_dict
284
+ cdef python.PyObject* dict_result
285
+ c_dict = python.PyDict_GetItem(
286
+ self._function_cache, None if c_ns_uri is NULL else c_ns_uri)
287
+ if c_dict is not NULL:
288
+ dict_result = python.PyDict_GetItem(
289
+ <object>c_dict, <unsigned char*>c_name)
290
+ if dict_result is not NULL:
291
+ return <object>dict_result
292
+ return None
293
+
294
+ # Python access to the XPath context for extension functions
295
+
296
+ @property
297
+ def context_node(self):
298
+ cdef xmlNode* c_node
299
+ if self._xpathCtxt is NULL:
300
+ raise XPathError, \
301
+ "XPath context is only usable during the evaluation"
302
+ c_node = self._xpathCtxt.node
303
+ if c_node is NULL:
304
+ raise XPathError, "no context node"
305
+ if c_node.doc != self._xpathCtxt.doc:
306
+ raise XPathError, \
307
+ "document-external context nodes are not supported"
308
+ if self._doc is None:
309
+ raise XPathError, "document context is missing"
310
+ return _elementFactory(self._doc, c_node)
311
+
312
+ @property
313
+ def eval_context(self):
314
+ if self._eval_context_dict is None:
315
+ self._eval_context_dict = {}
316
+ return self._eval_context_dict
317
+
318
+ # Python reference keeping during XPath function evaluation
319
+
320
+ @cython.final
321
+ cdef _release_temp_refs(self):
322
+ "Free temporarily referenced objects from this context."
323
+ self._temp_refs.clear()
324
+ self._temp_documents.clear()
325
+
326
+ @cython.final
327
+ cdef _hold(self, obj):
328
+ """A way to temporarily hold references to nodes in the evaluator.
329
+
330
+ This is needed because otherwise nodes created in XPath extension
331
+ functions would be reference counted too soon, during the XPath
332
+ evaluation. This is most important in the case of exceptions.
333
+ """
334
+ cdef _Element element
335
+ if isinstance(obj, _Element):
336
+ self._temp_refs.add(obj)
337
+ self._temp_documents.add((<_Element>obj)._doc)
338
+ return
339
+ elif _isString(obj) or not python.PySequence_Check(obj):
340
+ return
341
+ for o in obj:
342
+ if isinstance(o, _Element):
343
+ #print "Holding element:", <int>element._c_node
344
+ self._temp_refs.add(o)
345
+ #print "Holding document:", <int>element._doc._c_doc
346
+ self._temp_documents.add((<_Element>o)._doc)
347
+
348
+ @cython.final
349
+ cdef _Document _findDocumentForNode(self, xmlNode* c_node):
350
+ """If an XPath expression returns an element from a different
351
+ document than the current context document, we call this to
352
+ see if it was possibly created by an extension and is a known
353
+ document instance.
354
+ """
355
+ cdef _Document doc
356
+ for doc in self._temp_documents:
357
+ if doc is not None and doc._c_doc is c_node.doc:
358
+ return doc
359
+ return None
360
+
361
+
362
+ # libxml2 keeps these error messages in a static array in its code
363
+ # and doesn't give us access to them ...
364
+
365
+ cdef tuple LIBXML2_XPATH_ERROR_MESSAGES = (
366
+ b"Ok",
367
+ b"Number encoding",
368
+ b"Unfinished literal",
369
+ b"Start of literal",
370
+ b"Expected $ for variable reference",
371
+ b"Undefined variable",
372
+ b"Invalid predicate",
373
+ b"Invalid expression",
374
+ b"Missing closing curly brace",
375
+ b"Unregistered function",
376
+ b"Invalid operand",
377
+ b"Invalid type",
378
+ b"Invalid number of arguments",
379
+ b"Invalid context size",
380
+ b"Invalid context position",
381
+ b"Memory allocation error",
382
+ b"Syntax error",
383
+ b"Resource error",
384
+ b"Sub resource error",
385
+ b"Undefined namespace prefix",
386
+ b"Encoding error",
387
+ b"Char out of XML range",
388
+ b"Invalid or incomplete context",
389
+ b"Stack usage error",
390
+ b"Forbidden variable\n",
391
+ b"?? Unknown error ??\n",
392
+ )
393
+
394
+ cdef void _forwardXPathError(void* c_ctxt, const xmlerror.xmlError* c_error) noexcept with gil:
395
+ cdef xmlerror.xmlError error
396
+ cdef int xpath_code
397
+ if c_error.message is not NULL:
398
+ error.message = c_error.message
399
+ else:
400
+ xpath_code = c_error.code - xmlerror.XML_XPATH_EXPRESSION_OK
401
+ if 0 <= xpath_code < len(LIBXML2_XPATH_ERROR_MESSAGES):
402
+ error.message = _cstr(LIBXML2_XPATH_ERROR_MESSAGES[xpath_code])
403
+ else:
404
+ error.message = b"unknown error"
405
+ error.domain = c_error.domain
406
+ error.code = c_error.code
407
+ error.level = c_error.level
408
+ error.line = c_error.line
409
+ error.int2 = c_error.int1 # column
410
+ error.file = c_error.file
411
+ error.node = NULL
412
+
413
+ (<_BaseContext>c_ctxt)._error_log._receive(&error)
414
+
415
+ cdef void _receiveXPathError(void* c_context, const xmlerror.xmlError* error) noexcept nogil:
416
+ if not __DEBUG:
417
+ return
418
+ if c_context is NULL:
419
+ _forwardError(NULL, error)
420
+ else:
421
+ _forwardXPathError(c_context, error)
422
+
423
+
424
+ def Extension(module, function_mapping=None, *, ns=None):
425
+ """Extension(module, function_mapping=None, ns=None)
426
+
427
+ Build a dictionary of extension functions from the functions
428
+ defined in a module or the methods of an object.
429
+
430
+ As second argument, you can pass an additional mapping of
431
+ attribute names to XPath function names, or a list of function
432
+ names that should be taken.
433
+
434
+ The ``ns`` keyword argument accepts a namespace URI for the XPath
435
+ functions.
436
+ """
437
+ cdef dict functions = {}
438
+ if isinstance(function_mapping, dict):
439
+ for function_name, xpath_name in function_mapping.items():
440
+ functions[(ns, xpath_name)] = getattr(module, function_name)
441
+ else:
442
+ if function_mapping is None:
443
+ function_mapping = [ name for name in dir(module)
444
+ if not name.startswith('_') ]
445
+ for function_name in function_mapping:
446
+ functions[(ns, function_name)] = getattr(module, function_name)
447
+ return functions
448
+
449
+ ################################################################################
450
+ # EXSLT regexp implementation
451
+
452
+ @cython.final
453
+ @cython.internal
454
+ cdef class _ExsltRegExp:
455
+ cdef dict _compile_map
456
+ def __cinit__(self):
457
+ self._compile_map = {}
458
+
459
+ cdef _make_string(self, value):
460
+ if _isString(value):
461
+ return value
462
+ elif isinstance(value, list):
463
+ # node set: take recursive text concatenation of first element
464
+ if python.PyList_GET_SIZE(value) == 0:
465
+ return ''
466
+ firstnode = value[0]
467
+ if _isString(firstnode):
468
+ return firstnode
469
+ elif isinstance(firstnode, _Element):
470
+ c_text = tree.xmlNodeGetContent((<_Element>firstnode)._c_node)
471
+ if c_text is NULL:
472
+ raise MemoryError()
473
+ try:
474
+ return funicode(c_text)
475
+ finally:
476
+ tree.xmlFree(c_text)
477
+ else:
478
+ return unicode(firstnode)
479
+ else:
480
+ return unicode(value)
481
+
482
+ cdef _compile(self, rexp, ignore_case):
483
+ cdef python.PyObject* c_result
484
+ rexp = self._make_string(rexp)
485
+ key = (rexp, ignore_case)
486
+ c_result = python.PyDict_GetItem(self._compile_map, key)
487
+ if c_result is not NULL:
488
+ return <object>c_result
489
+ py_flags = re.UNICODE
490
+ if ignore_case:
491
+ py_flags = py_flags | re.IGNORECASE
492
+ rexp_compiled = re.compile(rexp, py_flags)
493
+ self._compile_map[key] = rexp_compiled
494
+ return rexp_compiled
495
+
496
+ def test(self, ctxt, s, rexp, flags=''):
497
+ flags = self._make_string(flags)
498
+ s = self._make_string(s)
499
+ rexpc = self._compile(rexp, 'i' in flags)
500
+ if rexpc.search(s) is None:
501
+ return False
502
+ else:
503
+ return True
504
+
505
+ def match(self, ctxt, s, rexp, flags=''):
506
+ cdef list result_list
507
+ flags = self._make_string(flags)
508
+ s = self._make_string(s)
509
+ rexpc = self._compile(rexp, 'i' in flags)
510
+ if 'g' in flags:
511
+ results = rexpc.findall(s)
512
+ if not results:
513
+ return ()
514
+ else:
515
+ result = rexpc.search(s)
516
+ if not result:
517
+ return ()
518
+ results = [ result.group() ]
519
+ results.extend( result.groups('') )
520
+ result_list = []
521
+ root = Element('matches')
522
+ for s_match in results:
523
+ if python.PyTuple_CheckExact(s_match):
524
+ s_match = ''.join(s_match)
525
+ elem = SubElement(root, 'match')
526
+ elem.text = s_match
527
+ result_list.append(elem)
528
+ return result_list
529
+
530
+ def replace(self, ctxt, s, rexp, flags, replacement):
531
+ replacement = self._make_string(replacement)
532
+ flags = self._make_string(flags)
533
+ s = self._make_string(s)
534
+ rexpc = self._compile(rexp, 'i' in flags)
535
+ count: object = 0 if 'g' in flags else 1
536
+ return rexpc.sub(replacement, s, count)
537
+
538
+ cdef _register_in_context(self, _BaseContext context):
539
+ ns = b"http://exslt.org/regular-expressions"
540
+ context._addLocalExtensionFunction(ns, b"test", self.test)
541
+ context._addLocalExtensionFunction(ns, b"match", self.match)
542
+ context._addLocalExtensionFunction(ns, b"replace", self.replace)
543
+
544
+
545
+ ################################################################################
546
+ # helper functions
547
+
548
+ cdef xpath.xmlXPathObject* _wrapXPathObject(object obj, _Document doc,
549
+ _BaseContext context) except NULL:
550
+ cdef xpath.xmlNodeSet* resultSet
551
+ cdef _Element fake_node = None
552
+ cdef xmlNode* c_node
553
+
554
+ if isinstance(obj, unicode):
555
+ obj = _utf8(obj)
556
+ if isinstance(obj, bytes):
557
+ # libxml2 copies the string value
558
+ return xpath.xmlXPathNewCString(_cstr(obj))
559
+ if isinstance(obj, bool):
560
+ return xpath.xmlXPathNewBoolean(obj)
561
+ if python.PyNumber_Check(obj):
562
+ return xpath.xmlXPathNewFloat(obj)
563
+ if obj is None:
564
+ resultSet = xpath.xmlXPathNodeSetCreate(NULL)
565
+ elif isinstance(obj, _Element):
566
+ resultSet = xpath.xmlXPathNodeSetCreate((<_Element>obj)._c_node)
567
+ elif python.PySequence_Check(obj):
568
+ resultSet = xpath.xmlXPathNodeSetCreate(NULL)
569
+ try:
570
+ for value in obj:
571
+ if isinstance(value, _Element):
572
+ if context is not None:
573
+ context._hold(value)
574
+ xpath.xmlXPathNodeSetAdd(resultSet, (<_Element>value)._c_node)
575
+ else:
576
+ if context is None or doc is None:
577
+ raise XPathResultError, \
578
+ f"Non-Element values not supported at this point - got {value!r}"
579
+ # support strings by appending text nodes to an Element
580
+ if isinstance(value, unicode):
581
+ value = _utf8(value)
582
+ if isinstance(value, bytes):
583
+ if fake_node is None:
584
+ fake_node = _makeElement("text-root", NULL, doc, None,
585
+ None, None, None, None, None)
586
+ context._hold(fake_node)
587
+ else:
588
+ # append a comment node to keep the text nodes separate
589
+ c_node = tree.xmlNewDocComment(doc._c_doc, <unsigned char*>"")
590
+ if c_node is NULL:
591
+ raise MemoryError()
592
+ tree.xmlAddChild(fake_node._c_node, c_node)
593
+ context._hold(value)
594
+ c_node = tree.xmlNewDocText(doc._c_doc, _xcstr(value))
595
+ if c_node is NULL:
596
+ raise MemoryError()
597
+ tree.xmlAddChild(fake_node._c_node, c_node)
598
+ xpath.xmlXPathNodeSetAdd(resultSet, c_node)
599
+ else:
600
+ raise XPathResultError, \
601
+ f"This is not a supported node-set result: {value!r}"
602
+ except:
603
+ xpath.xmlXPathFreeNodeSet(resultSet)
604
+ raise
605
+ else:
606
+ raise XPathResultError, f"Unknown return type: {python._fqtypename(obj).decode('utf8')}"
607
+ return xpath.xmlXPathWrapNodeSet(resultSet)
608
+
609
+ cdef object _unwrapXPathObject(xpath.xmlXPathObject* xpathObj,
610
+ _Document doc, _BaseContext context):
611
+ if xpathObj.type == xpath.XPATH_UNDEFINED:
612
+ raise XPathResultError, "Undefined xpath result"
613
+ elif xpathObj.type == xpath.XPATH_NODESET:
614
+ return _createNodeSetResult(xpathObj, doc, context)
615
+ elif xpathObj.type == xpath.XPATH_BOOLEAN:
616
+ return xpathObj.boolval
617
+ elif xpathObj.type == xpath.XPATH_NUMBER:
618
+ return xpathObj.floatval
619
+ elif xpathObj.type == xpath.XPATH_STRING:
620
+ stringval = funicode(xpathObj.stringval)
621
+ if context._build_smart_strings:
622
+ stringval = _elementStringResultFactory(
623
+ stringval, None, None, False)
624
+ return stringval
625
+ elif xpathObj.type == xpath.XPATH_POINT:
626
+ raise NotImplementedError, "XPATH_POINT"
627
+ elif xpathObj.type == xpath.XPATH_RANGE:
628
+ raise NotImplementedError, "XPATH_RANGE"
629
+ elif xpathObj.type == xpath.XPATH_LOCATIONSET:
630
+ raise NotImplementedError, "XPATH_LOCATIONSET"
631
+ elif xpathObj.type == xpath.XPATH_USERS:
632
+ raise NotImplementedError, "XPATH_USERS"
633
+ elif xpathObj.type == xpath.XPATH_XSLT_TREE:
634
+ return _createNodeSetResult(xpathObj, doc, context)
635
+ else:
636
+ raise XPathResultError, f"Unknown xpath result {xpathObj.type}"
637
+
638
+ cdef object _createNodeSetResult(xpath.xmlXPathObject* xpathObj, _Document doc,
639
+ _BaseContext context):
640
+ cdef xmlNode* c_node
641
+ cdef int i
642
+ cdef list result
643
+ result = []
644
+ if xpathObj.nodesetval is NULL:
645
+ return result
646
+ for i in range(xpathObj.nodesetval.nodeNr):
647
+ c_node = xpathObj.nodesetval.nodeTab[i]
648
+ _unpackNodeSetEntry(result, c_node, doc, context,
649
+ xpathObj.type == xpath.XPATH_XSLT_TREE)
650
+ return result
651
+
652
+ cdef _unpackNodeSetEntry(list results, xmlNode* c_node, _Document doc,
653
+ _BaseContext context, bint is_fragment):
654
+ cdef xmlNode* c_child
655
+ if _isElement(c_node):
656
+ if c_node.doc != doc._c_doc and c_node.doc._private is NULL:
657
+ # XXX: works, but maybe not always the right thing to do?
658
+ # XPath: only runs when extensions create or copy trees
659
+ # -> we store Python refs to these, so that is OK
660
+ # XSLT: can it leak when merging trees from multiple sources?
661
+ c_node = tree.xmlDocCopyNode(c_node, doc._c_doc, 1)
662
+ # FIXME: call _instantiateElementFromXPath() instead?
663
+ results.append(
664
+ _fakeDocElementFactory(doc, c_node))
665
+ elif c_node.type == tree.XML_TEXT_NODE or \
666
+ c_node.type == tree.XML_CDATA_SECTION_NODE or \
667
+ c_node.type == tree.XML_ATTRIBUTE_NODE:
668
+ results.append(
669
+ _buildElementStringResult(doc, c_node, context))
670
+ elif c_node.type == tree.XML_NAMESPACE_DECL:
671
+ results.append( (funicodeOrNone((<xmlNs*>c_node).prefix),
672
+ funicodeOrNone((<xmlNs*>c_node).href)) )
673
+ elif c_node.type == tree.XML_DOCUMENT_NODE or \
674
+ c_node.type == tree.XML_HTML_DOCUMENT_NODE:
675
+ # ignored for everything but result tree fragments
676
+ if is_fragment:
677
+ c_child = c_node.children
678
+ while c_child is not NULL:
679
+ _unpackNodeSetEntry(results, c_child, doc, context, 0)
680
+ c_child = c_child.next
681
+ elif c_node.type == tree.XML_XINCLUDE_START or \
682
+ c_node.type == tree.XML_XINCLUDE_END:
683
+ pass
684
+ else:
685
+ raise NotImplementedError, \
686
+ f"Not yet implemented result node type: {c_node.type}"
687
+
688
+ cdef void _freeXPathObject(xpath.xmlXPathObject* xpathObj) noexcept:
689
+ """Free the XPath object, but *never* free the *content* of node sets.
690
+ Python dealloc will do that for us.
691
+ """
692
+ if xpathObj.nodesetval is not NULL:
693
+ xpath.xmlXPathFreeNodeSet(xpathObj.nodesetval)
694
+ xpathObj.nodesetval = NULL
695
+ xpath.xmlXPathFreeObject(xpathObj)
696
+
697
+ cdef _Element _instantiateElementFromXPath(xmlNode* c_node, _Document doc,
698
+ _BaseContext context):
699
+ # NOTE: this may copy the element - only call this when it can't leak
700
+ if c_node.doc != doc._c_doc and c_node.doc._private is NULL:
701
+ # not from the context document and not from a fake document
702
+ # either => may still be from a known document, e.g. one
703
+ # created by an extension function
704
+ node_doc = context._findDocumentForNode(c_node)
705
+ if node_doc is None:
706
+ # not from a known document at all! => can only make a
707
+ # safety copy here
708
+ c_node = tree.xmlDocCopyNode(c_node, doc._c_doc, 1)
709
+ else:
710
+ doc = node_doc
711
+ return _fakeDocElementFactory(doc, c_node)
712
+
713
+ ################################################################################
714
+ # special str/unicode subclasses
715
+
716
+ @cython.final
717
+ cdef class _ElementUnicodeResult(unicode):
718
+ cdef _Element _parent
719
+ cdef readonly object attrname
720
+ cdef readonly bint is_tail
721
+
722
+ def getparent(self):
723
+ return self._parent
724
+
725
+ @property
726
+ def is_text(self):
727
+ return self._parent is not None and not (self.is_tail or self.attrname is not None)
728
+
729
+ @property
730
+ def is_attribute(self):
731
+ return self.attrname is not None
732
+
733
+ cdef object _elementStringResultFactory(string_value, _Element parent,
734
+ attrname, bint is_tail):
735
+ result = _ElementUnicodeResult(string_value)
736
+ result._parent = parent
737
+ result.is_tail = is_tail
738
+ result.attrname = attrname
739
+ return result
740
+
741
+ cdef object _buildElementStringResult(_Document doc, xmlNode* c_node,
742
+ _BaseContext context):
743
+ cdef _Element parent = None
744
+ cdef object attrname = None
745
+ cdef xmlNode* c_element
746
+ cdef bint is_tail
747
+
748
+ if c_node.type == tree.XML_ATTRIBUTE_NODE:
749
+ attrname = _namespacedName(c_node)
750
+ is_tail = 0
751
+ s = tree.xmlNodeGetContent(c_node)
752
+ try:
753
+ value = funicode(s)
754
+ finally:
755
+ tree.xmlFree(s)
756
+ c_element = NULL
757
+ else:
758
+ #assert c_node.type == tree.XML_TEXT_NODE or c_node.type == tree.XML_CDATA_SECTION_NODE, "invalid node type"
759
+ # may be tail text or normal text
760
+ value = funicode(c_node.content)
761
+ c_element = _previousElement(c_node)
762
+ is_tail = c_element is not NULL
763
+
764
+ if not context._build_smart_strings:
765
+ return value
766
+
767
+ if c_element is NULL:
768
+ # non-tail text or attribute text
769
+ c_element = c_node.parent
770
+ while c_element is not NULL and not _isElement(c_element):
771
+ c_element = c_element.parent
772
+
773
+ if c_element is not NULL:
774
+ parent = _instantiateElementFromXPath(c_element, doc, context)
775
+
776
+ return _elementStringResultFactory(
777
+ value, parent, attrname, is_tail)
778
+
779
+ ################################################################################
780
+ # callbacks for XPath/XSLT extension functions
781
+
782
+ cdef void _extension_function_call(_BaseContext context, function,
783
+ xpath.xmlXPathParserContext* ctxt, int nargs) noexcept:
784
+ cdef _Document doc
785
+ cdef xpath.xmlXPathObject* obj
786
+ cdef list args
787
+ cdef int i
788
+ doc = context._doc
789
+ try:
790
+ args = []
791
+ for i in range(nargs):
792
+ obj = xpath.valuePop(ctxt)
793
+ o = _unwrapXPathObject(obj, doc, context)
794
+ _freeXPathObject(obj)
795
+ args.append(o)
796
+ args.reverse()
797
+
798
+ res = function(context, *args)
799
+ # wrap result for XPath consumption
800
+ obj = _wrapXPathObject(res, doc, context)
801
+ # prevent Python from deallocating elements handed to libxml2
802
+ context._hold(res)
803
+ xpath.valuePush(ctxt, obj)
804
+ except:
805
+ xpath.xmlXPathErr(ctxt, xpath.XPATH_EXPR_ERROR)
806
+ context._exc._store_raised()
807
+ finally:
808
+ return # swallow any further exceptions
809
+
810
+ # lookup the function by name and call it
811
+
812
+ cdef void _xpath_function_call(xpath.xmlXPathParserContext* ctxt,
813
+ int nargs) noexcept with gil:
814
+ cdef _BaseContext context
815
+ cdef xpath.xmlXPathContext* rctxt = ctxt.context
816
+ context = <_BaseContext> rctxt.userData
817
+ try:
818
+ function = context._find_cached_function(rctxt.functionURI, rctxt.function)
819
+ if function is not None:
820
+ _extension_function_call(context, function, ctxt, nargs)
821
+ else:
822
+ xpath.xmlXPathErr(ctxt, xpath.XPATH_UNKNOWN_FUNC_ERROR)
823
+ context._exc._store_exception(XPathFunctionError(
824
+ f"XPath function '{_namespacedNameFromNsName(rctxt.functionURI, rctxt.function)}' not found"))
825
+ except:
826
+ # may not be the right error, but we need to tell libxml2 *something*
827
+ xpath.xmlXPathErr(ctxt, xpath.XPATH_UNKNOWN_FUNC_ERROR)
828
+ context._exc._store_raised()
829
+ finally:
830
+ return # swallow any further exceptions