lxml 6.0.0__cp311-cp311-manylinux_2_31_armv7l.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.
- lxml/ElementInclude.py +244 -0
- lxml/__init__.py +22 -0
- lxml/_elementpath.cpython-311-arm-linux-gnueabihf.so +0 -0
- lxml/_elementpath.py +343 -0
- lxml/apihelpers.pxi +1801 -0
- lxml/builder.cpython-311-arm-linux-gnueabihf.so +0 -0
- lxml/builder.py +243 -0
- lxml/classlookup.pxi +580 -0
- lxml/cleanup.pxi +215 -0
- lxml/cssselect.py +101 -0
- lxml/debug.pxi +36 -0
- lxml/docloader.pxi +178 -0
- lxml/doctestcompare.py +488 -0
- lxml/dtd.pxi +479 -0
- lxml/etree.cpython-311-arm-linux-gnueabihf.so +0 -0
- lxml/etree.h +244 -0
- lxml/etree.pyx +3853 -0
- lxml/etree_api.h +204 -0
- lxml/extensions.pxi +830 -0
- lxml/html/ElementSoup.py +10 -0
- lxml/html/__init__.py +1927 -0
- lxml/html/_diffcommand.py +86 -0
- lxml/html/_difflib.cpython-311-arm-linux-gnueabihf.so +0 -0
- lxml/html/_difflib.py +2106 -0
- lxml/html/_html5builder.py +100 -0
- lxml/html/_setmixin.py +56 -0
- lxml/html/builder.py +173 -0
- lxml/html/clean.py +21 -0
- lxml/html/defs.py +135 -0
- lxml/html/diff.cpython-311-arm-linux-gnueabihf.so +0 -0
- lxml/html/diff.py +972 -0
- lxml/html/formfill.py +299 -0
- lxml/html/html5parser.py +260 -0
- lxml/html/soupparser.py +314 -0
- lxml/html/usedoctest.py +13 -0
- lxml/includes/__init__.pxd +0 -0
- lxml/includes/__init__.py +0 -0
- lxml/includes/c14n.pxd +25 -0
- lxml/includes/config.pxd +3 -0
- lxml/includes/dtdvalid.pxd +18 -0
- lxml/includes/etree_defs.h +379 -0
- lxml/includes/etreepublic.pxd +237 -0
- lxml/includes/extlibs/__init__.py +0 -0
- lxml/includes/extlibs/libcharset.h +45 -0
- lxml/includes/extlibs/localcharset.h +137 -0
- lxml/includes/extlibs/zconf.h +543 -0
- lxml/includes/extlibs/zlib.h +1938 -0
- lxml/includes/htmlparser.pxd +56 -0
- lxml/includes/libexslt/__init__.py +0 -0
- lxml/includes/libexslt/exslt.h +108 -0
- lxml/includes/libexslt/exsltconfig.h +70 -0
- lxml/includes/libexslt/exsltexports.h +63 -0
- lxml/includes/libxml/HTMLparser.h +339 -0
- lxml/includes/libxml/HTMLtree.h +148 -0
- lxml/includes/libxml/SAX.h +18 -0
- lxml/includes/libxml/SAX2.h +170 -0
- lxml/includes/libxml/__init__.py +0 -0
- lxml/includes/libxml/c14n.h +115 -0
- lxml/includes/libxml/catalog.h +183 -0
- lxml/includes/libxml/chvalid.h +230 -0
- lxml/includes/libxml/debugXML.h +79 -0
- lxml/includes/libxml/dict.h +82 -0
- lxml/includes/libxml/encoding.h +307 -0
- lxml/includes/libxml/entities.h +147 -0
- lxml/includes/libxml/globals.h +25 -0
- lxml/includes/libxml/hash.h +251 -0
- lxml/includes/libxml/list.h +137 -0
- lxml/includes/libxml/nanoftp.h +16 -0
- lxml/includes/libxml/nanohttp.h +98 -0
- lxml/includes/libxml/parser.h +1633 -0
- lxml/includes/libxml/parserInternals.h +591 -0
- lxml/includes/libxml/relaxng.h +224 -0
- lxml/includes/libxml/schemasInternals.h +959 -0
- lxml/includes/libxml/schematron.h +143 -0
- lxml/includes/libxml/threads.h +81 -0
- lxml/includes/libxml/tree.h +1326 -0
- lxml/includes/libxml/uri.h +106 -0
- lxml/includes/libxml/valid.h +485 -0
- lxml/includes/libxml/xinclude.h +141 -0
- lxml/includes/libxml/xlink.h +193 -0
- lxml/includes/libxml/xmlIO.h +419 -0
- lxml/includes/libxml/xmlautomata.h +163 -0
- lxml/includes/libxml/xmlerror.h +962 -0
- lxml/includes/libxml/xmlexports.h +96 -0
- lxml/includes/libxml/xmlmemory.h +188 -0
- lxml/includes/libxml/xmlmodule.h +61 -0
- lxml/includes/libxml/xmlreader.h +444 -0
- lxml/includes/libxml/xmlregexp.h +116 -0
- lxml/includes/libxml/xmlsave.h +111 -0
- lxml/includes/libxml/xmlschemas.h +254 -0
- lxml/includes/libxml/xmlschemastypes.h +152 -0
- lxml/includes/libxml/xmlstring.h +140 -0
- lxml/includes/libxml/xmlunicode.h +15 -0
- lxml/includes/libxml/xmlversion.h +332 -0
- lxml/includes/libxml/xmlwriter.h +489 -0
- lxml/includes/libxml/xpath.h +569 -0
- lxml/includes/libxml/xpathInternals.h +639 -0
- lxml/includes/libxml/xpointer.h +48 -0
- lxml/includes/libxslt/__init__.py +0 -0
- lxml/includes/libxslt/attributes.h +39 -0
- lxml/includes/libxslt/documents.h +93 -0
- lxml/includes/libxslt/extensions.h +262 -0
- lxml/includes/libxslt/extra.h +72 -0
- lxml/includes/libxslt/functions.h +78 -0
- lxml/includes/libxslt/imports.h +75 -0
- lxml/includes/libxslt/keys.h +53 -0
- lxml/includes/libxslt/namespaces.h +68 -0
- lxml/includes/libxslt/numbersInternals.h +73 -0
- lxml/includes/libxslt/pattern.h +84 -0
- lxml/includes/libxslt/preproc.h +43 -0
- lxml/includes/libxslt/security.h +104 -0
- lxml/includes/libxslt/templates.h +77 -0
- lxml/includes/libxslt/transform.h +207 -0
- lxml/includes/libxslt/variables.h +118 -0
- lxml/includes/libxslt/xslt.h +110 -0
- lxml/includes/libxslt/xsltInternals.h +1995 -0
- lxml/includes/libxslt/xsltconfig.h +146 -0
- lxml/includes/libxslt/xsltexports.h +64 -0
- lxml/includes/libxslt/xsltlocale.h +44 -0
- lxml/includes/libxslt/xsltutils.h +343 -0
- lxml/includes/lxml-version.h +3 -0
- lxml/includes/relaxng.pxd +64 -0
- lxml/includes/schematron.pxd +34 -0
- lxml/includes/tree.pxd +492 -0
- lxml/includes/uri.pxd +5 -0
- lxml/includes/xinclude.pxd +22 -0
- lxml/includes/xmlerror.pxd +852 -0
- lxml/includes/xmlparser.pxd +303 -0
- lxml/includes/xmlschema.pxd +35 -0
- lxml/includes/xpath.pxd +136 -0
- lxml/includes/xslt.pxd +190 -0
- lxml/isoschematron/__init__.py +348 -0
- lxml/isoschematron/resources/rng/iso-schematron.rng +709 -0
- lxml/isoschematron/resources/xsl/RNG2Schtrn.xsl +75 -0
- lxml/isoschematron/resources/xsl/XSD2Schtrn.xsl +77 -0
- lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_abstract_expand.xsl +313 -0
- lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_dsdl_include.xsl +1160 -0
- lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_schematron_message.xsl +55 -0
- lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_schematron_skeleton_for_xslt1.xsl +1796 -0
- lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_svrl_for_xslt1.xsl +588 -0
- lxml/isoschematron/resources/xsl/iso-schematron-xslt1/readme.txt +84 -0
- lxml/iterparse.pxi +438 -0
- lxml/lxml.etree.h +244 -0
- lxml/lxml.etree_api.h +204 -0
- lxml/nsclasses.pxi +281 -0
- lxml/objectify.cpython-311-arm-linux-gnueabihf.so +0 -0
- lxml/objectify.pyx +2149 -0
- lxml/objectpath.pxi +332 -0
- lxml/parser.pxi +2059 -0
- lxml/parsertarget.pxi +180 -0
- lxml/proxy.pxi +619 -0
- lxml/public-api.pxi +178 -0
- lxml/pyclasslookup.py +3 -0
- lxml/readonlytree.pxi +565 -0
- lxml/relaxng.pxi +165 -0
- lxml/sax.cpython-311-arm-linux-gnueabihf.so +0 -0
- lxml/sax.py +286 -0
- lxml/saxparser.pxi +875 -0
- lxml/schematron.pxi +173 -0
- lxml/serializer.pxi +1849 -0
- lxml/usedoctest.py +13 -0
- lxml/xinclude.pxi +67 -0
- lxml/xmlerror.pxi +1654 -0
- lxml/xmlid.pxi +179 -0
- lxml/xmlschema.pxi +215 -0
- lxml/xpath.pxi +487 -0
- lxml/xslt.pxi +957 -0
- lxml/xsltext.pxi +242 -0
- lxml-6.0.0.dist-info/METADATA +163 -0
- lxml-6.0.0.dist-info/RECORD +174 -0
- lxml-6.0.0.dist-info/WHEEL +5 -0
- lxml-6.0.0.dist-info/licenses/LICENSE.txt +31 -0
- lxml-6.0.0.dist-info/licenses/LICENSES.txt +29 -0
- lxml-6.0.0.dist-info/top_level.txt +1 -0
lxml/etree.pyx
ADDED
@@ -0,0 +1,3853 @@
|
|
1
|
+
# cython: binding=True
|
2
|
+
# cython: auto_pickle=False
|
3
|
+
# cython: language_level=3
|
4
|
+
|
5
|
+
"""
|
6
|
+
The ``lxml.etree`` module implements the extended ElementTree API for XML.
|
7
|
+
"""
|
8
|
+
|
9
|
+
__docformat__ = "restructuredtext en"
|
10
|
+
|
11
|
+
__all__ = [
|
12
|
+
'AttributeBasedElementClassLookup', 'C14NError', 'C14NWriterTarget', 'CDATA',
|
13
|
+
'Comment', 'CommentBase', 'CustomElementClassLookup', 'DEBUG',
|
14
|
+
'DTD', 'DTDError', 'DTDParseError', 'DTDValidateError',
|
15
|
+
'DocumentInvalid', 'ETCompatXMLParser', 'ETXPath', 'Element',
|
16
|
+
'ElementBase', 'ElementClassLookup', 'ElementDefaultClassLookup',
|
17
|
+
'ElementNamespaceClassLookup', 'ElementTree', 'Entity', 'EntityBase',
|
18
|
+
'Error', 'ErrorDomains', 'ErrorLevels', 'ErrorTypes', 'Extension',
|
19
|
+
'FallbackElementClassLookup', 'FunctionNamespace', 'HTML', 'HTMLParser',
|
20
|
+
'ICONV_COMPILED_VERSION',
|
21
|
+
'LIBXML_COMPILED_VERSION', 'LIBXML_VERSION',
|
22
|
+
'LIBXML_FEATURES',
|
23
|
+
'LIBXSLT_COMPILED_VERSION', 'LIBXSLT_VERSION',
|
24
|
+
'LXML_VERSION',
|
25
|
+
'LxmlError', 'LxmlRegistryError', 'LxmlSyntaxError',
|
26
|
+
'NamespaceRegistryError', 'PI', 'PIBase', 'ParseError',
|
27
|
+
'ParserBasedElementClassLookup', 'ParserError', 'ProcessingInstruction',
|
28
|
+
'PyErrorLog', 'PythonElementClassLookup', 'QName', 'RelaxNG',
|
29
|
+
'RelaxNGError', 'RelaxNGErrorTypes', 'RelaxNGParseError',
|
30
|
+
'RelaxNGValidateError', 'Resolver', 'Schematron', 'SchematronError',
|
31
|
+
'SchematronParseError', 'SchematronValidateError', 'SerialisationError',
|
32
|
+
'SubElement', 'TreeBuilder', 'XInclude', 'XIncludeError', 'XML',
|
33
|
+
'XMLDTDID', 'XMLID', 'XMLParser', 'XMLSchema', 'XMLSchemaError',
|
34
|
+
'XMLSchemaParseError', 'XMLSchemaValidateError', 'XMLSyntaxError',
|
35
|
+
'XMLTreeBuilder', 'XPath', 'XPathDocumentEvaluator', 'XPathError',
|
36
|
+
'XPathEvalError', 'XPathEvaluator', 'XPathFunctionError', 'XPathResultError',
|
37
|
+
'XPathSyntaxError', 'XSLT', 'XSLTAccessControl', 'XSLTApplyError',
|
38
|
+
'XSLTError', 'XSLTExtension', 'XSLTExtensionError', 'XSLTParseError',
|
39
|
+
'XSLTSaveError', 'canonicalize',
|
40
|
+
'cleanup_namespaces', 'clear_error_log', 'dump',
|
41
|
+
'fromstring', 'fromstringlist', 'get_default_parser', 'iselement',
|
42
|
+
'iterparse', 'iterwalk', 'parse', 'parseid', 'register_namespace',
|
43
|
+
'set_default_parser', 'set_element_class_lookup', 'strip_attributes',
|
44
|
+
'strip_elements', 'strip_tags', 'tostring', 'tostringlist', 'tounicode',
|
45
|
+
'use_global_python_log'
|
46
|
+
]
|
47
|
+
|
48
|
+
cimport cython
|
49
|
+
|
50
|
+
from lxml cimport python
|
51
|
+
from lxml.includes cimport tree, config
|
52
|
+
from lxml.includes.tree cimport xmlDoc, xmlNode, xmlAttr, xmlNs, _isElement, _getNs
|
53
|
+
from lxml.includes.tree cimport const_xmlChar, xmlChar, _xcstr
|
54
|
+
from lxml.python cimport _cstr, _isString
|
55
|
+
from lxml.includes cimport xpath
|
56
|
+
from lxml.includes cimport c14n
|
57
|
+
|
58
|
+
# Cython's standard declarations
|
59
|
+
cimport cpython.mem
|
60
|
+
cimport cpython.ref
|
61
|
+
from libc cimport limits, stdio, stdlib
|
62
|
+
from libc cimport string as cstring_h # not to be confused with stdlib 'string'
|
63
|
+
from libc.string cimport const_char
|
64
|
+
|
65
|
+
cdef object os_path_abspath
|
66
|
+
from os.path import abspath as os_path_abspath
|
67
|
+
|
68
|
+
cdef object BytesIO, StringIO
|
69
|
+
from io import BytesIO, StringIO
|
70
|
+
|
71
|
+
cdef object OrderedDict
|
72
|
+
from collections import OrderedDict
|
73
|
+
|
74
|
+
cdef object _elementpath
|
75
|
+
from lxml import _elementpath
|
76
|
+
|
77
|
+
cdef object sys
|
78
|
+
import sys
|
79
|
+
|
80
|
+
cdef object re
|
81
|
+
import re
|
82
|
+
|
83
|
+
cdef object partial
|
84
|
+
from functools import partial
|
85
|
+
|
86
|
+
cdef object islice
|
87
|
+
from itertools import islice
|
88
|
+
|
89
|
+
cdef object ITER_EMPTY = iter(())
|
90
|
+
|
91
|
+
cdef object MutableMapping
|
92
|
+
from collections.abc import MutableMapping
|
93
|
+
|
94
|
+
class _ImmutableMapping(MutableMapping):
|
95
|
+
def __getitem__(self, key):
|
96
|
+
raise KeyError, key
|
97
|
+
|
98
|
+
def __setitem__(self, key, value):
|
99
|
+
raise KeyError, key
|
100
|
+
|
101
|
+
def __delitem__(self, key):
|
102
|
+
raise KeyError, key
|
103
|
+
|
104
|
+
def __contains__(self, key):
|
105
|
+
return False
|
106
|
+
|
107
|
+
def __len__(self):
|
108
|
+
return 0
|
109
|
+
|
110
|
+
def __iter__(self):
|
111
|
+
return ITER_EMPTY
|
112
|
+
iterkeys = itervalues = iteritems = __iter__
|
113
|
+
|
114
|
+
cdef object IMMUTABLE_EMPTY_MAPPING = _ImmutableMapping()
|
115
|
+
del _ImmutableMapping
|
116
|
+
|
117
|
+
|
118
|
+
# the rules
|
119
|
+
# ---------
|
120
|
+
# any libxml C argument/variable is prefixed with c_
|
121
|
+
# any non-public function/class is prefixed with an underscore
|
122
|
+
# instance creation is always through factories
|
123
|
+
|
124
|
+
# what to do with libxml2/libxslt error messages?
|
125
|
+
# 0 : drop
|
126
|
+
# 1 : use log
|
127
|
+
DEF __DEBUG = 1
|
128
|
+
|
129
|
+
# maximum number of lines in the libxml2/xslt log if __DEBUG == 1
|
130
|
+
DEF __MAX_LOG_SIZE = 100
|
131
|
+
|
132
|
+
# make the compiled-in debug state publicly available
|
133
|
+
DEBUG = __DEBUG
|
134
|
+
|
135
|
+
# A struct to store a cached qualified tag name+href pair.
|
136
|
+
# While we can borrow the c_name from the document dict,
|
137
|
+
# PyPy requires us to store a Python reference for the
|
138
|
+
# namespace in order to keep the byte buffer alive.
|
139
|
+
cdef struct qname:
|
140
|
+
const_xmlChar* c_name
|
141
|
+
python.PyObject* href
|
142
|
+
|
143
|
+
# initialize parser (and threading)
|
144
|
+
xmlparser.xmlInitParser()
|
145
|
+
|
146
|
+
# global per-thread setup
|
147
|
+
tree.xmlThrDefIndentTreeOutput(1)
|
148
|
+
tree.xmlThrDefLineNumbersDefaultValue(1)
|
149
|
+
|
150
|
+
_initThreadLogging()
|
151
|
+
|
152
|
+
# filename encoding
|
153
|
+
cdef bytes _FILENAME_ENCODING = (sys.getfilesystemencoding() or sys.getdefaultencoding() or 'ascii').encode("UTF-8")
|
154
|
+
cdef char* _C_FILENAME_ENCODING = _cstr(_FILENAME_ENCODING)
|
155
|
+
|
156
|
+
# set up some default namespace prefixes
|
157
|
+
cdef dict _DEFAULT_NAMESPACE_PREFIXES = {
|
158
|
+
b"http://www.w3.org/XML/1998/namespace": b'xml',
|
159
|
+
b"http://www.w3.org/1999/xhtml": b"html",
|
160
|
+
b"http://www.w3.org/1999/XSL/Transform": b"xsl",
|
161
|
+
b"http://www.w3.org/1999/02/22-rdf-syntax-ns#": b"rdf",
|
162
|
+
b"http://schemas.xmlsoap.org/wsdl/": b"wsdl",
|
163
|
+
# xml schema
|
164
|
+
b"http://www.w3.org/2001/XMLSchema": b"xs",
|
165
|
+
b"http://www.w3.org/2001/XMLSchema-instance": b"xsi",
|
166
|
+
# dublin core
|
167
|
+
b"http://purl.org/dc/elements/1.1/": b"dc",
|
168
|
+
# objectify
|
169
|
+
b"http://codespeak.net/lxml/objectify/pytype" : b"py",
|
170
|
+
}
|
171
|
+
|
172
|
+
# To avoid runtime encoding overhead, we keep a Unicode copy
|
173
|
+
# of the uri-prefix mapping as (str, str) items view.
|
174
|
+
cdef object _DEFAULT_NAMESPACE_PREFIXES_ITEMS = []
|
175
|
+
|
176
|
+
cdef _update_default_namespace_prefixes_items():
|
177
|
+
cdef bytes ns, prefix
|
178
|
+
global _DEFAULT_NAMESPACE_PREFIXES_ITEMS
|
179
|
+
_DEFAULT_NAMESPACE_PREFIXES_ITEMS = {
|
180
|
+
ns.decode('utf-8') : prefix.decode('utf-8')
|
181
|
+
for ns, prefix in _DEFAULT_NAMESPACE_PREFIXES.items()
|
182
|
+
}.items()
|
183
|
+
|
184
|
+
_update_default_namespace_prefixes_items()
|
185
|
+
|
186
|
+
cdef object _check_internal_prefix = re.compile(br"ns\d+$").match
|
187
|
+
|
188
|
+
def register_namespace(prefix, uri):
|
189
|
+
"""Registers a namespace prefix that newly created Elements in that
|
190
|
+
namespace will use. The registry is global, and any existing
|
191
|
+
mapping for either the given prefix or the namespace URI will be
|
192
|
+
removed.
|
193
|
+
"""
|
194
|
+
prefix_utf, uri_utf = _utf8(prefix), _utf8(uri)
|
195
|
+
if _check_internal_prefix(prefix_utf):
|
196
|
+
raise ValueError("Prefix format reserved for internal use")
|
197
|
+
_tagValidOrRaise(prefix_utf)
|
198
|
+
_uriValidOrRaise(uri_utf)
|
199
|
+
if (uri_utf == b"http://www.w3.org/XML/1998/namespace" and prefix_utf != b'xml'
|
200
|
+
or prefix_utf == b'xml' and uri_utf != b"http://www.w3.org/XML/1998/namespace"):
|
201
|
+
raise ValueError("Cannot change the 'xml' prefix of the XML namespace")
|
202
|
+
for k, v in list(_DEFAULT_NAMESPACE_PREFIXES.items()):
|
203
|
+
if k == uri_utf or v == prefix_utf:
|
204
|
+
del _DEFAULT_NAMESPACE_PREFIXES[k]
|
205
|
+
_DEFAULT_NAMESPACE_PREFIXES[uri_utf] = prefix_utf
|
206
|
+
_update_default_namespace_prefixes_items()
|
207
|
+
|
208
|
+
|
209
|
+
# Error superclass for ElementTree compatibility
|
210
|
+
cdef class Error(Exception):
|
211
|
+
pass
|
212
|
+
|
213
|
+
# module level superclass for all exceptions
|
214
|
+
cdef class LxmlError(Error):
|
215
|
+
"""Main exception base class for lxml. All other exceptions inherit from
|
216
|
+
this one.
|
217
|
+
"""
|
218
|
+
def __init__(self, message, error_log=None):
|
219
|
+
super(_Error, self).__init__(message)
|
220
|
+
if error_log is None:
|
221
|
+
self.error_log = __copyGlobalErrorLog()
|
222
|
+
else:
|
223
|
+
self.error_log = error_log.copy()
|
224
|
+
|
225
|
+
cdef object _Error = Error
|
226
|
+
|
227
|
+
|
228
|
+
# superclass for all syntax errors
|
229
|
+
class LxmlSyntaxError(LxmlError, SyntaxError):
|
230
|
+
"""Base class for all syntax errors.
|
231
|
+
"""
|
232
|
+
|
233
|
+
cdef class C14NError(LxmlError):
|
234
|
+
"""Error during C14N serialisation.
|
235
|
+
"""
|
236
|
+
|
237
|
+
# version information
|
238
|
+
cdef tuple __unpackDottedVersion(version):
|
239
|
+
version_list = []
|
240
|
+
l = (version.decode("ascii").replace('-', '.').split('.') + [0]*4)[:4]
|
241
|
+
for item in l:
|
242
|
+
try:
|
243
|
+
item = int(item)
|
244
|
+
except ValueError:
|
245
|
+
if item.startswith('dev'):
|
246
|
+
count = item[3:]
|
247
|
+
item = -300
|
248
|
+
elif item.startswith('alpha'):
|
249
|
+
count = item[5:]
|
250
|
+
item = -200
|
251
|
+
elif item.startswith('beta'):
|
252
|
+
count = item[4:]
|
253
|
+
item = -100
|
254
|
+
else:
|
255
|
+
count = 0
|
256
|
+
if count:
|
257
|
+
item += int(count)
|
258
|
+
version_list.append(item)
|
259
|
+
return tuple(version_list)
|
260
|
+
|
261
|
+
cdef tuple __unpackIntVersion(int c_version, int base=100):
|
262
|
+
return (
|
263
|
+
((c_version // (base*base)) % base),
|
264
|
+
((c_version // base) % base),
|
265
|
+
(c_version % base)
|
266
|
+
)
|
267
|
+
|
268
|
+
cdef int _LIBXML_VERSION_INT
|
269
|
+
try:
|
270
|
+
_LIBXML_VERSION_INT = int(
|
271
|
+
re.match('[0-9]+', (<unsigned char*>tree.xmlParserVersion).decode("ascii")).group(0))
|
272
|
+
except Exception:
|
273
|
+
print("Unknown libxml2 version: " + (<unsigned char*>tree.xmlParserVersion).decode("latin1"))
|
274
|
+
_LIBXML_VERSION_INT = 0
|
275
|
+
|
276
|
+
LIBXML_VERSION = __unpackIntVersion(_LIBXML_VERSION_INT)
|
277
|
+
LIBXML_COMPILED_VERSION = __unpackIntVersion(tree.LIBXML_VERSION)
|
278
|
+
LXML_VERSION = __unpackDottedVersion(tree.LXML_VERSION_STRING)
|
279
|
+
|
280
|
+
__version__ = tree.LXML_VERSION_STRING.decode("ascii")
|
281
|
+
|
282
|
+
cdef extern from *:
|
283
|
+
"""
|
284
|
+
#ifdef ZLIB_VERNUM
|
285
|
+
#define __lxml_zlib_version (ZLIB_VERNUM >> 4)
|
286
|
+
#else
|
287
|
+
#define __lxml_zlib_version 0
|
288
|
+
#endif
|
289
|
+
#ifdef _LIBICONV_VERSION
|
290
|
+
#define __lxml_iconv_version (_LIBICONV_VERSION << 8)
|
291
|
+
#else
|
292
|
+
#define __lxml_iconv_version 0
|
293
|
+
#endif
|
294
|
+
"""
|
295
|
+
# zlib isn't included automatically by libxml2's headers
|
296
|
+
#long ZLIB_HEX_VERSION "__lxml_zlib_version"
|
297
|
+
long LIBICONV_HEX_VERSION "__lxml_iconv_version"
|
298
|
+
|
299
|
+
#ZLIB_COMPILED_VERSION = __unpackIntVersion(ZLIB_HEX_VERSION, base=0x10)
|
300
|
+
ICONV_COMPILED_VERSION = __unpackIntVersion(LIBICONV_HEX_VERSION, base=0x100)[:2]
|
301
|
+
|
302
|
+
|
303
|
+
cdef extern from "libxml/xmlversion.h":
|
304
|
+
"""
|
305
|
+
static const char* const _lxml_lib_features[] = {
|
306
|
+
#ifdef LIBXML_HTML_ENABLED
|
307
|
+
"html",
|
308
|
+
#endif
|
309
|
+
#ifdef LIBXML_FTP_ENABLED
|
310
|
+
"ftp",
|
311
|
+
#endif
|
312
|
+
#ifdef LIBXML_HTTP_ENABLED
|
313
|
+
"http",
|
314
|
+
#endif
|
315
|
+
#ifdef LIBXML_CATALOG_ENABLED
|
316
|
+
"catalog",
|
317
|
+
#endif
|
318
|
+
#ifdef LIBXML_XPATH_ENABLED
|
319
|
+
"xpath",
|
320
|
+
#endif
|
321
|
+
#ifdef LIBXML_ICONV_ENABLED
|
322
|
+
"iconv",
|
323
|
+
#endif
|
324
|
+
#ifdef LIBXML_ICU_ENABLED
|
325
|
+
"icu",
|
326
|
+
#endif
|
327
|
+
#ifdef LIBXML_REGEXP_ENABLED
|
328
|
+
"regexp",
|
329
|
+
#endif
|
330
|
+
#ifdef LIBXML_SCHEMAS_ENABLED
|
331
|
+
"xmlschema",
|
332
|
+
#endif
|
333
|
+
#ifdef LIBXML_SCHEMATRON_ENABLED
|
334
|
+
"schematron",
|
335
|
+
#endif
|
336
|
+
#ifdef LIBXML_ZLIB_ENABLED
|
337
|
+
"zlib",
|
338
|
+
#endif
|
339
|
+
#ifdef LIBXML_LZMA_ENABLED
|
340
|
+
"lzma",
|
341
|
+
#endif
|
342
|
+
0
|
343
|
+
};
|
344
|
+
"""
|
345
|
+
const char* const* _LXML_LIB_FEATURES "_lxml_lib_features"
|
346
|
+
|
347
|
+
|
348
|
+
cdef set _copy_lib_features():
|
349
|
+
features = set()
|
350
|
+
feature = _LXML_LIB_FEATURES
|
351
|
+
while feature[0]:
|
352
|
+
features.add(feature[0].decode('ASCII'))
|
353
|
+
feature += 1
|
354
|
+
return features
|
355
|
+
|
356
|
+
LIBXML_COMPILED_FEATURES = _copy_lib_features()
|
357
|
+
LIBXML_FEATURES = {
|
358
|
+
feature_name for feature_id, feature_name in [
|
359
|
+
#XML_WITH_THREAD = 1
|
360
|
+
#XML_WITH_TREE = 2
|
361
|
+
#XML_WITH_OUTPUT = 3
|
362
|
+
#XML_WITH_PUSH = 4
|
363
|
+
#XML_WITH_READER = 5
|
364
|
+
#XML_WITH_PATTERN = 6
|
365
|
+
#XML_WITH_WRITER = 7
|
366
|
+
#XML_WITH_SAX1 = 8
|
367
|
+
(xmlparser.XML_WITH_FTP, "ftp"), # XML_WITH_FTP = 9
|
368
|
+
(xmlparser.XML_WITH_HTTP, "http"), # XML_WITH_HTTP = 10
|
369
|
+
#XML_WITH_VALID = 11
|
370
|
+
(xmlparser.XML_WITH_HTML, "html"), # XML_WITH_HTML = 12
|
371
|
+
#XML_WITH_LEGACY = 13
|
372
|
+
#XML_WITH_C14N = 14
|
373
|
+
(xmlparser.XML_WITH_CATALOG, "catalog"), # XML_WITH_CATALOG = 15
|
374
|
+
(xmlparser.XML_WITH_XPATH, "xpath"), # XML_WITH_XPATH = 16
|
375
|
+
#XML_WITH_XPTR = 17
|
376
|
+
#XML_WITH_XINCLUDE = 18
|
377
|
+
(xmlparser.XML_WITH_ICONV, "iconv"), # XML_WITH_ICONV = 19
|
378
|
+
#XML_WITH_ISO8859X = 20
|
379
|
+
#XML_WITH_UNICODE = 21
|
380
|
+
(xmlparser.XML_WITH_REGEXP, "regexp"), # XML_WITH_REGEXP = 22
|
381
|
+
#XML_WITH_AUTOMATA = 23
|
382
|
+
#XML_WITH_EXPR = 24
|
383
|
+
(xmlparser.XML_WITH_SCHEMAS, "xmlschema"), # XML_WITH_SCHEMAS = 25
|
384
|
+
(xmlparser.XML_WITH_SCHEMATRON, "schematron"), # XML_WITH_SCHEMATRON = 26
|
385
|
+
#XML_WITH_MODULES = 27
|
386
|
+
#XML_WITH_DEBUG = 28
|
387
|
+
#XML_WITH_DEBUG_MEM = 29
|
388
|
+
#XML_WITH_DEBUG_RUN = 30 # unused
|
389
|
+
(xmlparser.XML_WITH_ZLIB, "zlib"), # XML_WITH_ZLIB = 31
|
390
|
+
(xmlparser.XML_WITH_ICU, "icu"), # XML_WITH_ICU = 32
|
391
|
+
(xmlparser.XML_WITH_LZMA, "lzma"), # XML_WITH_LZMA = 33
|
392
|
+
] if xmlparser.xmlHasFeature(feature_id)
|
393
|
+
}
|
394
|
+
|
395
|
+
cdef bint HAS_ZLIB_COMPRESSION = xmlparser.xmlHasFeature(xmlparser.XML_WITH_ZLIB)
|
396
|
+
|
397
|
+
|
398
|
+
# class for temporary storage of Python references,
|
399
|
+
# used e.g. for XPath results
|
400
|
+
@cython.final
|
401
|
+
@cython.internal
|
402
|
+
cdef class _TempStore:
|
403
|
+
cdef list _storage
|
404
|
+
def __init__(self):
|
405
|
+
self._storage = []
|
406
|
+
|
407
|
+
cdef int add(self, obj) except -1:
|
408
|
+
self._storage.append(obj)
|
409
|
+
return 0
|
410
|
+
|
411
|
+
cdef int clear(self) except -1:
|
412
|
+
del self._storage[:]
|
413
|
+
return 0
|
414
|
+
|
415
|
+
|
416
|
+
# class for temporarily storing exceptions raised in extensions
|
417
|
+
@cython.internal
|
418
|
+
cdef class _ExceptionContext:
|
419
|
+
cdef object _exc_info
|
420
|
+
cdef int clear(self) except -1:
|
421
|
+
self._exc_info = None
|
422
|
+
return 0
|
423
|
+
|
424
|
+
cdef void _store_raised(self) noexcept:
|
425
|
+
try:
|
426
|
+
self._exc_info = sys.exc_info()
|
427
|
+
except BaseException as e:
|
428
|
+
self._store_exception(e)
|
429
|
+
finally:
|
430
|
+
return # and swallow any further exceptions
|
431
|
+
|
432
|
+
cdef int _store_exception(self, exception) except -1:
|
433
|
+
self._exc_info = (exception, None, None)
|
434
|
+
return 0
|
435
|
+
|
436
|
+
cdef bint _has_raised(self) except -1:
|
437
|
+
return self._exc_info is not None
|
438
|
+
|
439
|
+
cdef int _raise_if_stored(self) except -1:
|
440
|
+
if self._exc_info is None:
|
441
|
+
return 0
|
442
|
+
type, value, traceback = self._exc_info
|
443
|
+
self._exc_info = None
|
444
|
+
if value is None and traceback is None:
|
445
|
+
raise type
|
446
|
+
else:
|
447
|
+
raise type, value, traceback
|
448
|
+
|
449
|
+
|
450
|
+
# type of a function that steps from node to node
|
451
|
+
ctypedef public xmlNode* (*_node_to_node_function)(xmlNode*)
|
452
|
+
|
453
|
+
|
454
|
+
################################################################################
|
455
|
+
# Include submodules
|
456
|
+
|
457
|
+
include "proxy.pxi" # Proxy handling (element backpointers/memory/etc.)
|
458
|
+
include "apihelpers.pxi" # Private helper functions
|
459
|
+
include "xmlerror.pxi" # Error and log handling
|
460
|
+
|
461
|
+
|
462
|
+
################################################################################
|
463
|
+
# Public Python API
|
464
|
+
|
465
|
+
@cython.final
|
466
|
+
@cython.freelist(8)
|
467
|
+
cdef public class _Document [ type LxmlDocumentType, object LxmlDocument ]:
|
468
|
+
"""Internal base class to reference a libxml document.
|
469
|
+
|
470
|
+
When instances of this class are garbage collected, the libxml
|
471
|
+
document is cleaned up.
|
472
|
+
"""
|
473
|
+
cdef int _ns_counter
|
474
|
+
cdef bytes _prefix_tail
|
475
|
+
cdef xmlDoc* _c_doc
|
476
|
+
cdef _BaseParser _parser
|
477
|
+
|
478
|
+
def __dealloc__(self):
|
479
|
+
# if there are no more references to the document, it is safe
|
480
|
+
# to clean the whole thing up, as all nodes have a reference to
|
481
|
+
# the document
|
482
|
+
tree.xmlFreeDoc(self._c_doc)
|
483
|
+
|
484
|
+
@cython.final
|
485
|
+
cdef getroot(self):
|
486
|
+
# return an element proxy for the document root
|
487
|
+
cdef xmlNode* c_node
|
488
|
+
c_node = tree.xmlDocGetRootElement(self._c_doc)
|
489
|
+
if c_node is NULL:
|
490
|
+
return None
|
491
|
+
return _elementFactory(self, c_node)
|
492
|
+
|
493
|
+
@cython.final
|
494
|
+
cdef bint hasdoctype(self) noexcept:
|
495
|
+
# DOCTYPE gets parsed into internal subset (xmlDTD*)
|
496
|
+
return self._c_doc is not NULL and self._c_doc.intSubset is not NULL
|
497
|
+
|
498
|
+
@cython.final
|
499
|
+
cdef getdoctype(self):
|
500
|
+
# get doctype info: root tag, public/system ID (or None if not known)
|
501
|
+
cdef tree.xmlDtd* c_dtd
|
502
|
+
cdef xmlNode* c_root_node
|
503
|
+
public_id = None
|
504
|
+
sys_url = None
|
505
|
+
c_dtd = self._c_doc.intSubset
|
506
|
+
if c_dtd is not NULL:
|
507
|
+
if c_dtd.ExternalID is not NULL:
|
508
|
+
public_id = funicode(c_dtd.ExternalID)
|
509
|
+
if c_dtd.SystemID is not NULL:
|
510
|
+
sys_url = funicode(c_dtd.SystemID)
|
511
|
+
c_dtd = self._c_doc.extSubset
|
512
|
+
if c_dtd is not NULL:
|
513
|
+
if not public_id and c_dtd.ExternalID is not NULL:
|
514
|
+
public_id = funicode(c_dtd.ExternalID)
|
515
|
+
if not sys_url and c_dtd.SystemID is not NULL:
|
516
|
+
sys_url = funicode(c_dtd.SystemID)
|
517
|
+
c_root_node = tree.xmlDocGetRootElement(self._c_doc)
|
518
|
+
if c_root_node is NULL:
|
519
|
+
root_name = None
|
520
|
+
else:
|
521
|
+
root_name = funicode(c_root_node.name)
|
522
|
+
return root_name, public_id, sys_url
|
523
|
+
|
524
|
+
@cython.final
|
525
|
+
cdef getxmlinfo(self):
|
526
|
+
# return XML version and encoding (or None if not known)
|
527
|
+
cdef xmlDoc* c_doc = self._c_doc
|
528
|
+
if c_doc.version is NULL:
|
529
|
+
version = None
|
530
|
+
else:
|
531
|
+
version = funicode(c_doc.version)
|
532
|
+
if c_doc.encoding is NULL:
|
533
|
+
encoding = None
|
534
|
+
else:
|
535
|
+
encoding = funicode(c_doc.encoding)
|
536
|
+
return version, encoding
|
537
|
+
|
538
|
+
@cython.final
|
539
|
+
cdef isstandalone(self):
|
540
|
+
# returns True for "standalone=true",
|
541
|
+
# False for "standalone=false", None if not provided
|
542
|
+
if self._c_doc.standalone == -1:
|
543
|
+
return None
|
544
|
+
else:
|
545
|
+
return <bint>(self._c_doc.standalone == 1)
|
546
|
+
|
547
|
+
@cython.final
|
548
|
+
cdef bytes buildNewPrefix(self):
|
549
|
+
# get a new unique prefix ("nsX") for this document
|
550
|
+
cdef bytes ns
|
551
|
+
if self._ns_counter < len(_PREFIX_CACHE):
|
552
|
+
ns = _PREFIX_CACHE[self._ns_counter]
|
553
|
+
else:
|
554
|
+
ns = python.PyBytes_FromFormat("ns%d", self._ns_counter)
|
555
|
+
if self._prefix_tail is not None:
|
556
|
+
ns += self._prefix_tail
|
557
|
+
self._ns_counter += 1
|
558
|
+
if self._ns_counter < 0:
|
559
|
+
# overflow!
|
560
|
+
self._ns_counter = 0
|
561
|
+
if self._prefix_tail is None:
|
562
|
+
self._prefix_tail = b"A"
|
563
|
+
else:
|
564
|
+
self._prefix_tail += b"A"
|
565
|
+
return ns
|
566
|
+
|
567
|
+
@cython.final
|
568
|
+
cdef xmlNs* _findOrBuildNodeNs(self, xmlNode* c_node,
|
569
|
+
const_xmlChar* c_href, const_xmlChar* c_prefix,
|
570
|
+
bint is_attribute) except NULL:
|
571
|
+
"""Get or create namespace structure for a node. Reuses the prefix if
|
572
|
+
possible.
|
573
|
+
"""
|
574
|
+
cdef xmlNs* c_ns
|
575
|
+
cdef xmlNs* c_doc_ns
|
576
|
+
cdef python.PyObject* dict_result
|
577
|
+
if c_node.type != tree.XML_ELEMENT_NODE:
|
578
|
+
assert c_node.type == tree.XML_ELEMENT_NODE, \
|
579
|
+
"invalid node type %d, expected %d" % (
|
580
|
+
c_node.type, tree.XML_ELEMENT_NODE)
|
581
|
+
# look for existing ns declaration
|
582
|
+
c_ns = _searchNsByHref(c_node, c_href, is_attribute)
|
583
|
+
if c_ns is not NULL:
|
584
|
+
if is_attribute and c_ns.prefix is NULL:
|
585
|
+
# do not put namespaced attributes into the default
|
586
|
+
# namespace as this would break serialisation
|
587
|
+
pass
|
588
|
+
else:
|
589
|
+
return c_ns
|
590
|
+
|
591
|
+
# none found => determine a suitable new prefix
|
592
|
+
if c_prefix is NULL:
|
593
|
+
dict_result = python.PyDict_GetItem(
|
594
|
+
_DEFAULT_NAMESPACE_PREFIXES, <unsigned char*>c_href)
|
595
|
+
if dict_result is not NULL:
|
596
|
+
prefix = <object>dict_result
|
597
|
+
else:
|
598
|
+
prefix = self.buildNewPrefix()
|
599
|
+
c_prefix = _xcstr(prefix)
|
600
|
+
|
601
|
+
# make sure the prefix is not in use already
|
602
|
+
while tree.xmlSearchNs(self._c_doc, c_node, c_prefix) is not NULL:
|
603
|
+
prefix = self.buildNewPrefix()
|
604
|
+
c_prefix = _xcstr(prefix)
|
605
|
+
|
606
|
+
# declare the namespace and return it
|
607
|
+
c_ns = tree.xmlNewNs(c_node, c_href, c_prefix)
|
608
|
+
if c_ns is NULL:
|
609
|
+
raise MemoryError()
|
610
|
+
return c_ns
|
611
|
+
|
612
|
+
@cython.final
|
613
|
+
cdef int _setNodeNs(self, xmlNode* c_node, const_xmlChar* c_href) except -1:
|
614
|
+
"Lookup namespace structure and set it for the node."
|
615
|
+
c_ns = self._findOrBuildNodeNs(c_node, c_href, NULL, 0)
|
616
|
+
tree.xmlSetNs(c_node, c_ns)
|
617
|
+
|
618
|
+
|
619
|
+
cdef tuple __initPrefixCache():
|
620
|
+
cdef int i
|
621
|
+
return tuple([ python.PyBytes_FromFormat("ns%d", i)
|
622
|
+
for i in range(26) ])
|
623
|
+
|
624
|
+
cdef tuple _PREFIX_CACHE = __initPrefixCache()
|
625
|
+
|
626
|
+
|
627
|
+
cdef _Document _documentFactory(xmlDoc* c_doc, _BaseParser parser):
|
628
|
+
cdef _Document result
|
629
|
+
result = _Document.__new__(_Document)
|
630
|
+
result._c_doc = c_doc
|
631
|
+
result._ns_counter = 0
|
632
|
+
result._prefix_tail = None
|
633
|
+
if parser is None:
|
634
|
+
parser = __GLOBAL_PARSER_CONTEXT.getDefaultParser()
|
635
|
+
result._parser = parser
|
636
|
+
return result
|
637
|
+
|
638
|
+
|
639
|
+
cdef object _find_invalid_public_id_characters = re.compile(
|
640
|
+
ur"[^\x20\x0D\x0Aa-zA-Z0-9'()+,./:=?;!*#@$_%-]+").search
|
641
|
+
|
642
|
+
|
643
|
+
cdef class DocInfo:
|
644
|
+
"Document information provided by parser and DTD."
|
645
|
+
cdef _Document _doc
|
646
|
+
def __cinit__(self, tree):
|
647
|
+
"Create a DocInfo object for an ElementTree object or root Element."
|
648
|
+
self._doc = _documentOrRaise(tree)
|
649
|
+
root_name, public_id, system_url = self._doc.getdoctype()
|
650
|
+
if not root_name and (public_id or system_url):
|
651
|
+
raise ValueError, "Could not find root node"
|
652
|
+
|
653
|
+
@property
|
654
|
+
def root_name(self):
|
655
|
+
"""Returns the name of the root node as defined by the DOCTYPE."""
|
656
|
+
root_name, public_id, system_url = self._doc.getdoctype()
|
657
|
+
return root_name
|
658
|
+
|
659
|
+
@cython.final
|
660
|
+
cdef tree.xmlDtd* _get_c_dtd(self):
|
661
|
+
""""Return the DTD. Create it if it does not yet exist."""
|
662
|
+
cdef xmlDoc* c_doc = self._doc._c_doc
|
663
|
+
cdef xmlNode* c_root_node
|
664
|
+
cdef const_xmlChar* c_name
|
665
|
+
|
666
|
+
if c_doc.intSubset:
|
667
|
+
return c_doc.intSubset
|
668
|
+
|
669
|
+
c_root_node = tree.xmlDocGetRootElement(c_doc)
|
670
|
+
c_name = c_root_node.name if c_root_node else NULL
|
671
|
+
return tree.xmlCreateIntSubset(c_doc, c_name, NULL, NULL)
|
672
|
+
|
673
|
+
def clear(self):
|
674
|
+
"""Removes DOCTYPE and internal subset from the document."""
|
675
|
+
cdef xmlDoc* c_doc = self._doc._c_doc
|
676
|
+
cdef tree.xmlNode* c_dtd = <xmlNode*>c_doc.intSubset
|
677
|
+
if c_dtd is NULL:
|
678
|
+
return
|
679
|
+
tree.xmlUnlinkNode(c_dtd)
|
680
|
+
tree.xmlFreeNode(c_dtd)
|
681
|
+
|
682
|
+
property public_id:
|
683
|
+
"""Public ID of the DOCTYPE.
|
684
|
+
|
685
|
+
Mutable. May be set to a valid string or None. If a DTD does not
|
686
|
+
exist, setting this variable (even to None) will create one.
|
687
|
+
"""
|
688
|
+
def __get__(self):
|
689
|
+
root_name, public_id, system_url = self._doc.getdoctype()
|
690
|
+
return public_id
|
691
|
+
|
692
|
+
def __set__(self, value):
|
693
|
+
cdef xmlChar* c_value = NULL
|
694
|
+
if value is not None:
|
695
|
+
match = _find_invalid_public_id_characters(value)
|
696
|
+
if match:
|
697
|
+
raise ValueError, f'Invalid character(s) {match.group(0)!r} in public_id.'
|
698
|
+
value = _utf8(value)
|
699
|
+
c_value = tree.xmlStrdup(_xcstr(value))
|
700
|
+
if not c_value:
|
701
|
+
raise MemoryError()
|
702
|
+
|
703
|
+
c_dtd = self._get_c_dtd()
|
704
|
+
if not c_dtd:
|
705
|
+
tree.xmlFree(c_value)
|
706
|
+
raise MemoryError()
|
707
|
+
if c_dtd.ExternalID:
|
708
|
+
tree.xmlFree(<void*>c_dtd.ExternalID)
|
709
|
+
c_dtd.ExternalID = c_value
|
710
|
+
|
711
|
+
property system_url:
|
712
|
+
"""System ID of the DOCTYPE.
|
713
|
+
|
714
|
+
Mutable. May be set to a valid string or None. If a DTD does not
|
715
|
+
exist, setting this variable (even to None) will create one.
|
716
|
+
"""
|
717
|
+
def __get__(self):
|
718
|
+
root_name, public_id, system_url = self._doc.getdoctype()
|
719
|
+
return system_url
|
720
|
+
|
721
|
+
def __set__(self, value):
|
722
|
+
cdef xmlChar* c_value = NULL
|
723
|
+
if value is not None:
|
724
|
+
bvalue = _utf8(value)
|
725
|
+
# sys_url may be any valid unicode string that can be
|
726
|
+
# enclosed in single quotes or quotes.
|
727
|
+
if b"'" in bvalue and b'"' in bvalue:
|
728
|
+
raise ValueError(
|
729
|
+
'System URL may not contain both single (\') and double quotes (").')
|
730
|
+
c_value = tree.xmlStrdup(_xcstr(bvalue))
|
731
|
+
if not c_value:
|
732
|
+
raise MemoryError()
|
733
|
+
|
734
|
+
c_dtd = self._get_c_dtd()
|
735
|
+
if not c_dtd:
|
736
|
+
tree.xmlFree(c_value)
|
737
|
+
raise MemoryError()
|
738
|
+
if c_dtd.SystemID:
|
739
|
+
tree.xmlFree(<void*>c_dtd.SystemID)
|
740
|
+
c_dtd.SystemID = c_value
|
741
|
+
|
742
|
+
@property
|
743
|
+
def xml_version(self):
|
744
|
+
"""Returns the XML version as declared by the document."""
|
745
|
+
xml_version, encoding = self._doc.getxmlinfo()
|
746
|
+
return xml_version
|
747
|
+
|
748
|
+
@property
|
749
|
+
def encoding(self):
|
750
|
+
"""Returns the encoding name as declared by the document."""
|
751
|
+
xml_version, encoding = self._doc.getxmlinfo()
|
752
|
+
return encoding
|
753
|
+
|
754
|
+
@property
|
755
|
+
def standalone(self):
|
756
|
+
"""Returns the standalone flag as declared by the document. The possible
|
757
|
+
values are True (``standalone='yes'``), False
|
758
|
+
(``standalone='no'`` or flag not provided in the declaration),
|
759
|
+
and None (unknown or no declaration found). Note that a
|
760
|
+
normal truth test on this value will always tell if the
|
761
|
+
``standalone`` flag was set to ``'yes'`` or not.
|
762
|
+
"""
|
763
|
+
return self._doc.isstandalone()
|
764
|
+
|
765
|
+
property URL:
|
766
|
+
"The source URL of the document (or None if unknown)."
|
767
|
+
def __get__(self):
|
768
|
+
if self._doc._c_doc.URL is NULL:
|
769
|
+
return None
|
770
|
+
return _decodeFilename(self._doc._c_doc.URL)
|
771
|
+
def __set__(self, url):
|
772
|
+
url = _encodeFilename(url)
|
773
|
+
c_oldurl = self._doc._c_doc.URL
|
774
|
+
if url is None:
|
775
|
+
self._doc._c_doc.URL = NULL
|
776
|
+
else:
|
777
|
+
self._doc._c_doc.URL = tree.xmlStrdup(_xcstr(url))
|
778
|
+
if c_oldurl is not NULL:
|
779
|
+
tree.xmlFree(<void*>c_oldurl)
|
780
|
+
|
781
|
+
@property
|
782
|
+
def doctype(self):
|
783
|
+
"""Returns a DOCTYPE declaration string for the document."""
|
784
|
+
root_name, public_id, system_url = self._doc.getdoctype()
|
785
|
+
if system_url:
|
786
|
+
# If '"' in system_url, we must escape it with single
|
787
|
+
# quotes, otherwise escape with double quotes. If url
|
788
|
+
# contains both a single quote and a double quote, XML
|
789
|
+
# standard is being violated.
|
790
|
+
if '"' in system_url:
|
791
|
+
quoted_system_url = f"'{system_url}'"
|
792
|
+
else:
|
793
|
+
quoted_system_url = f'"{system_url}"'
|
794
|
+
if public_id:
|
795
|
+
if system_url:
|
796
|
+
return f'<!DOCTYPE {root_name} PUBLIC "{public_id}" {quoted_system_url}>'
|
797
|
+
else:
|
798
|
+
return f'<!DOCTYPE {root_name} PUBLIC "{public_id}">'
|
799
|
+
elif system_url:
|
800
|
+
return f'<!DOCTYPE {root_name} SYSTEM {quoted_system_url}>'
|
801
|
+
elif self._doc.hasdoctype():
|
802
|
+
return f'<!DOCTYPE {root_name}>'
|
803
|
+
else:
|
804
|
+
return ''
|
805
|
+
|
806
|
+
@property
|
807
|
+
def internalDTD(self):
|
808
|
+
"""Returns a DTD validator based on the internal subset of the document."""
|
809
|
+
return _dtdFactory(self._doc._c_doc.intSubset)
|
810
|
+
|
811
|
+
@property
|
812
|
+
def externalDTD(self):
|
813
|
+
"""Returns a DTD validator based on the external subset of the document."""
|
814
|
+
return _dtdFactory(self._doc._c_doc.extSubset)
|
815
|
+
|
816
|
+
|
817
|
+
@cython.no_gc_clear
|
818
|
+
cdef public class _Element [ type LxmlElementType, object LxmlElement ]:
|
819
|
+
"""Element class.
|
820
|
+
|
821
|
+
References a document object and a libxml node.
|
822
|
+
|
823
|
+
By pointing to a Document instance, a reference is kept to
|
824
|
+
_Document as long as there is some pointer to a node in it.
|
825
|
+
"""
|
826
|
+
cdef _Document _doc
|
827
|
+
cdef xmlNode* _c_node
|
828
|
+
cdef object _tag
|
829
|
+
|
830
|
+
def _init(self):
|
831
|
+
"""_init(self)
|
832
|
+
|
833
|
+
Called after object initialisation. Custom subclasses may override
|
834
|
+
this if they recursively call _init() in the superclasses.
|
835
|
+
"""
|
836
|
+
|
837
|
+
@cython.linetrace(False)
|
838
|
+
@cython.profile(False)
|
839
|
+
def __dealloc__(self):
|
840
|
+
#print("trying to free node:", <int>self._c_node)
|
841
|
+
#displayNode(self._c_node, 0)
|
842
|
+
if self._c_node is not NULL:
|
843
|
+
_unregisterProxy(self)
|
844
|
+
attemptDeallocation(self._c_node)
|
845
|
+
|
846
|
+
# MANIPULATORS
|
847
|
+
|
848
|
+
def __setitem__(self, x, value):
|
849
|
+
"""__setitem__(self, x, value)
|
850
|
+
|
851
|
+
Replaces the given subelement index or slice.
|
852
|
+
"""
|
853
|
+
cdef xmlNode* c_node = NULL
|
854
|
+
cdef xmlNode* c_next
|
855
|
+
cdef xmlDoc* c_source_doc
|
856
|
+
cdef _Element element
|
857
|
+
cdef bint left_to_right
|
858
|
+
cdef Py_ssize_t slicelength = 0, step = 0
|
859
|
+
_assertValidNode(self)
|
860
|
+
if value is None:
|
861
|
+
raise ValueError, "cannot assign None"
|
862
|
+
if isinstance(x, slice):
|
863
|
+
# slice assignment
|
864
|
+
_findChildSlice(<slice>x, self._c_node, &c_node, &step, &slicelength)
|
865
|
+
if step > 0:
|
866
|
+
left_to_right = 1
|
867
|
+
else:
|
868
|
+
left_to_right = 0
|
869
|
+
step = -step
|
870
|
+
_replaceSlice(self, c_node, slicelength, step, left_to_right, value)
|
871
|
+
return
|
872
|
+
else:
|
873
|
+
# otherwise: normal item assignment
|
874
|
+
element = value
|
875
|
+
_assertValidNode(element)
|
876
|
+
c_node = _findChild(self._c_node, x)
|
877
|
+
if c_node is NULL:
|
878
|
+
raise IndexError, "list index out of range"
|
879
|
+
c_source_doc = element._c_node.doc
|
880
|
+
c_next = element._c_node.next
|
881
|
+
_removeText(c_node.next)
|
882
|
+
tree.xmlReplaceNode(c_node, element._c_node)
|
883
|
+
_moveTail(c_next, element._c_node)
|
884
|
+
moveNodeToDocument(self._doc, c_source_doc, element._c_node)
|
885
|
+
if not attemptDeallocation(c_node):
|
886
|
+
moveNodeToDocument(self._doc, c_node.doc, c_node)
|
887
|
+
|
888
|
+
def __delitem__(self, x):
|
889
|
+
"""__delitem__(self, x)
|
890
|
+
|
891
|
+
Deletes the given subelement or a slice.
|
892
|
+
"""
|
893
|
+
cdef xmlNode* c_node = NULL
|
894
|
+
cdef xmlNode* c_next
|
895
|
+
cdef Py_ssize_t step = 0, slicelength = 0
|
896
|
+
_assertValidNode(self)
|
897
|
+
if isinstance(x, slice):
|
898
|
+
# slice deletion
|
899
|
+
if _isFullSlice(<slice>x):
|
900
|
+
c_node = self._c_node.children
|
901
|
+
if c_node is not NULL:
|
902
|
+
if not _isElement(c_node):
|
903
|
+
c_node = _nextElement(c_node)
|
904
|
+
while c_node is not NULL:
|
905
|
+
c_next = _nextElement(c_node)
|
906
|
+
_removeNode(self._doc, c_node)
|
907
|
+
c_node = c_next
|
908
|
+
else:
|
909
|
+
_findChildSlice(<slice>x, self._c_node, &c_node, &step, &slicelength)
|
910
|
+
_deleteSlice(self._doc, c_node, slicelength, step)
|
911
|
+
else:
|
912
|
+
# item deletion
|
913
|
+
c_node = _findChild(self._c_node, x)
|
914
|
+
if c_node is NULL:
|
915
|
+
raise IndexError, f"index out of range: {x}"
|
916
|
+
_removeNode(self._doc, c_node)
|
917
|
+
|
918
|
+
def __deepcopy__(self, memo):
|
919
|
+
"__deepcopy__(self, memo)"
|
920
|
+
return self.__copy__()
|
921
|
+
|
922
|
+
def __copy__(self):
|
923
|
+
"__copy__(self)"
|
924
|
+
cdef xmlDoc* c_doc
|
925
|
+
cdef xmlNode* c_node
|
926
|
+
cdef _Document new_doc
|
927
|
+
_assertValidNode(self)
|
928
|
+
c_doc = _copyDocRoot(self._doc._c_doc, self._c_node) # recursive
|
929
|
+
new_doc = _documentFactory(c_doc, self._doc._parser)
|
930
|
+
root = new_doc.getroot()
|
931
|
+
if root is not None:
|
932
|
+
return root
|
933
|
+
# Comment/PI
|
934
|
+
c_node = c_doc.children
|
935
|
+
while c_node is not NULL and c_node.type != self._c_node.type:
|
936
|
+
c_node = c_node.next
|
937
|
+
if c_node is NULL:
|
938
|
+
return None
|
939
|
+
return _elementFactory(new_doc, c_node)
|
940
|
+
|
941
|
+
def set(self, key, value):
|
942
|
+
"""set(self, key, value)
|
943
|
+
|
944
|
+
Sets an element attribute.
|
945
|
+
In HTML documents (not XML or XHTML), the value None is allowed and creates
|
946
|
+
an attribute without value (just the attribute name).
|
947
|
+
"""
|
948
|
+
_assertValidNode(self)
|
949
|
+
_setAttributeValue(self, key, value)
|
950
|
+
|
951
|
+
def append(self, _Element element not None):
|
952
|
+
"""append(self, element)
|
953
|
+
|
954
|
+
Adds a subelement to the end of this element.
|
955
|
+
"""
|
956
|
+
_assertValidNode(self)
|
957
|
+
_assertValidNode(element)
|
958
|
+
_appendChild(self, element)
|
959
|
+
|
960
|
+
def addnext(self, _Element element not None):
|
961
|
+
"""addnext(self, element)
|
962
|
+
|
963
|
+
Adds the element as a following sibling directly after this
|
964
|
+
element.
|
965
|
+
|
966
|
+
This is normally used to set a processing instruction or comment after
|
967
|
+
the root node of a document. Note that tail text is automatically
|
968
|
+
discarded when adding at the root level.
|
969
|
+
"""
|
970
|
+
_assertValidNode(self)
|
971
|
+
_assertValidNode(element)
|
972
|
+
if self._c_node.parent != NULL and not _isElement(self._c_node.parent):
|
973
|
+
if element._c_node.type not in (tree.XML_PI_NODE, tree.XML_COMMENT_NODE):
|
974
|
+
raise TypeError, "Only processing instructions and comments can be siblings of the root element"
|
975
|
+
element.tail = None
|
976
|
+
_appendSibling(self, element)
|
977
|
+
|
978
|
+
def addprevious(self, _Element element not None):
|
979
|
+
"""addprevious(self, element)
|
980
|
+
|
981
|
+
Adds the element as a preceding sibling directly before this
|
982
|
+
element.
|
983
|
+
|
984
|
+
This is normally used to set a processing instruction or comment
|
985
|
+
before the root node of a document. Note that tail text is
|
986
|
+
automatically discarded when adding at the root level.
|
987
|
+
"""
|
988
|
+
_assertValidNode(self)
|
989
|
+
_assertValidNode(element)
|
990
|
+
if self._c_node.parent != NULL and not _isElement(self._c_node.parent):
|
991
|
+
if element._c_node.type != tree.XML_PI_NODE:
|
992
|
+
if element._c_node.type != tree.XML_COMMENT_NODE:
|
993
|
+
raise TypeError, "Only processing instructions and comments can be siblings of the root element"
|
994
|
+
element.tail = None
|
995
|
+
_prependSibling(self, element)
|
996
|
+
|
997
|
+
def extend(self, elements):
|
998
|
+
"""extend(self, elements)
|
999
|
+
|
1000
|
+
Extends the current children by the elements in the iterable.
|
1001
|
+
"""
|
1002
|
+
cdef _Element element
|
1003
|
+
_assertValidNode(self)
|
1004
|
+
for element in elements:
|
1005
|
+
if element is None:
|
1006
|
+
raise TypeError, "Node must not be None"
|
1007
|
+
_assertValidNode(element)
|
1008
|
+
_appendChild(self, element)
|
1009
|
+
|
1010
|
+
def clear(self, bint keep_tail=False):
|
1011
|
+
"""clear(self, keep_tail=False)
|
1012
|
+
|
1013
|
+
Resets an element. This function removes all subelements, clears
|
1014
|
+
all attributes and sets the text and tail properties to None.
|
1015
|
+
|
1016
|
+
Pass ``keep_tail=True`` to leave the tail text untouched.
|
1017
|
+
"""
|
1018
|
+
cdef xmlAttr* c_attr
|
1019
|
+
cdef xmlAttr* c_attr_next
|
1020
|
+
cdef xmlNode* c_node
|
1021
|
+
cdef xmlNode* c_node_next
|
1022
|
+
_assertValidNode(self)
|
1023
|
+
c_node = self._c_node
|
1024
|
+
# remove self.text and self.tail
|
1025
|
+
_removeText(c_node.children)
|
1026
|
+
if not keep_tail:
|
1027
|
+
_removeText(c_node.next)
|
1028
|
+
# remove all attributes
|
1029
|
+
c_attr = c_node.properties
|
1030
|
+
if c_attr:
|
1031
|
+
c_node.properties = NULL
|
1032
|
+
tree.xmlFreePropList(c_attr)
|
1033
|
+
# remove all subelements
|
1034
|
+
c_node = c_node.children
|
1035
|
+
if c_node and not _isElement(c_node):
|
1036
|
+
c_node = _nextElement(c_node)
|
1037
|
+
while c_node is not NULL:
|
1038
|
+
c_node_next = _nextElement(c_node)
|
1039
|
+
_removeNode(self._doc, c_node)
|
1040
|
+
c_node = c_node_next
|
1041
|
+
|
1042
|
+
def insert(self, index: int, _Element element not None):
|
1043
|
+
"""insert(self, index, element)
|
1044
|
+
|
1045
|
+
Inserts a subelement at the given position in this element
|
1046
|
+
"""
|
1047
|
+
cdef xmlNode* c_node
|
1048
|
+
cdef xmlNode* c_next
|
1049
|
+
cdef xmlDoc* c_source_doc
|
1050
|
+
_assertValidNode(self)
|
1051
|
+
_assertValidNode(element)
|
1052
|
+
c_node = _findChild(self._c_node, index)
|
1053
|
+
if c_node is NULL:
|
1054
|
+
_appendChild(self, element)
|
1055
|
+
return
|
1056
|
+
# prevent cycles
|
1057
|
+
if _isAncestorOrSame(element._c_node, self._c_node):
|
1058
|
+
raise ValueError("cannot append parent to itself")
|
1059
|
+
c_source_doc = element._c_node.doc
|
1060
|
+
c_next = element._c_node.next
|
1061
|
+
tree.xmlAddPrevSibling(c_node, element._c_node)
|
1062
|
+
_moveTail(c_next, element._c_node)
|
1063
|
+
moveNodeToDocument(self._doc, c_source_doc, element._c_node)
|
1064
|
+
|
1065
|
+
def remove(self, _Element element not None):
|
1066
|
+
"""remove(self, element)
|
1067
|
+
|
1068
|
+
Removes a matching subelement. Unlike the find methods, this
|
1069
|
+
method compares elements based on identity, not on tag value
|
1070
|
+
or contents.
|
1071
|
+
"""
|
1072
|
+
cdef xmlNode* c_node
|
1073
|
+
cdef xmlNode* c_next
|
1074
|
+
_assertValidNode(self)
|
1075
|
+
_assertValidNode(element)
|
1076
|
+
c_node = element._c_node
|
1077
|
+
if c_node.parent is not self._c_node:
|
1078
|
+
raise ValueError, "Element is not a child of this node."
|
1079
|
+
c_next = element._c_node.next
|
1080
|
+
tree.xmlUnlinkNode(c_node)
|
1081
|
+
_moveTail(c_next, c_node)
|
1082
|
+
# fix namespace declarations
|
1083
|
+
moveNodeToDocument(self._doc, c_node.doc, c_node)
|
1084
|
+
|
1085
|
+
def replace(self, _Element old_element not None,
|
1086
|
+
_Element new_element not None):
|
1087
|
+
"""replace(self, old_element, new_element)
|
1088
|
+
|
1089
|
+
Replaces a subelement with the element passed as second argument.
|
1090
|
+
"""
|
1091
|
+
cdef xmlNode* c_old_node
|
1092
|
+
cdef xmlNode* c_old_next
|
1093
|
+
cdef xmlNode* c_new_node
|
1094
|
+
cdef xmlNode* c_new_next
|
1095
|
+
cdef xmlDoc* c_source_doc
|
1096
|
+
_assertValidNode(self)
|
1097
|
+
_assertValidNode(old_element)
|
1098
|
+
_assertValidNode(new_element)
|
1099
|
+
c_old_node = old_element._c_node
|
1100
|
+
if c_old_node.parent is not self._c_node:
|
1101
|
+
raise ValueError, "Element is not a child of this node."
|
1102
|
+
c_new_node = new_element._c_node
|
1103
|
+
# prevent cycles
|
1104
|
+
if _isAncestorOrSame(c_new_node, self._c_node):
|
1105
|
+
raise ValueError("cannot append parent to itself")
|
1106
|
+
# replace node
|
1107
|
+
c_old_next = c_old_node.next
|
1108
|
+
c_new_next = c_new_node.next
|
1109
|
+
c_source_doc = c_new_node.doc
|
1110
|
+
tree.xmlReplaceNode(c_old_node, c_new_node)
|
1111
|
+
_moveTail(c_new_next, c_new_node)
|
1112
|
+
_moveTail(c_old_next, c_old_node)
|
1113
|
+
moveNodeToDocument(self._doc, c_source_doc, c_new_node)
|
1114
|
+
# fix namespace declarations
|
1115
|
+
moveNodeToDocument(self._doc, c_old_node.doc, c_old_node)
|
1116
|
+
|
1117
|
+
# PROPERTIES
|
1118
|
+
property tag:
|
1119
|
+
"""Element tag
|
1120
|
+
"""
|
1121
|
+
def __get__(self):
|
1122
|
+
if self._tag is not None:
|
1123
|
+
return self._tag
|
1124
|
+
_assertValidNode(self)
|
1125
|
+
self._tag = _namespacedName(self._c_node)
|
1126
|
+
return self._tag
|
1127
|
+
|
1128
|
+
def __set__(self, value):
|
1129
|
+
cdef _BaseParser parser
|
1130
|
+
_assertValidNode(self)
|
1131
|
+
ns, name = _getNsTag(value)
|
1132
|
+
parser = self._doc._parser
|
1133
|
+
if parser is not None and parser._for_html:
|
1134
|
+
_htmlTagValidOrRaise(name)
|
1135
|
+
else:
|
1136
|
+
_tagValidOrRaise(name)
|
1137
|
+
self._tag = value
|
1138
|
+
tree.xmlNodeSetName(self._c_node, _xcstr(name))
|
1139
|
+
if ns is None:
|
1140
|
+
self._c_node.ns = NULL
|
1141
|
+
else:
|
1142
|
+
self._doc._setNodeNs(self._c_node, _xcstr(ns))
|
1143
|
+
|
1144
|
+
@property
|
1145
|
+
def attrib(self):
|
1146
|
+
"""Element attribute dictionary. Where possible, use get(), set(),
|
1147
|
+
keys(), values() and items() to access element attributes.
|
1148
|
+
"""
|
1149
|
+
return _Attrib.__new__(_Attrib, self)
|
1150
|
+
|
1151
|
+
property text:
|
1152
|
+
"""Text before the first subelement. This is either a string or
|
1153
|
+
the value None, if there was no text.
|
1154
|
+
"""
|
1155
|
+
def __get__(self):
|
1156
|
+
_assertValidNode(self)
|
1157
|
+
return _collectText(self._c_node.children)
|
1158
|
+
|
1159
|
+
def __set__(self, value):
|
1160
|
+
_assertValidNode(self)
|
1161
|
+
if isinstance(value, QName):
|
1162
|
+
value = _resolveQNameText(self, value).decode('utf8')
|
1163
|
+
_setNodeText(self._c_node, value)
|
1164
|
+
|
1165
|
+
# using 'del el.text' is the wrong thing to do
|
1166
|
+
#def __del__(self):
|
1167
|
+
# _setNodeText(self._c_node, None)
|
1168
|
+
|
1169
|
+
property tail:
|
1170
|
+
"""Text after this element's end tag, but before the next sibling
|
1171
|
+
element's start tag. This is either a string or the value None, if
|
1172
|
+
there was no text.
|
1173
|
+
"""
|
1174
|
+
def __get__(self):
|
1175
|
+
_assertValidNode(self)
|
1176
|
+
return _collectText(self._c_node.next)
|
1177
|
+
|
1178
|
+
def __set__(self, value):
|
1179
|
+
_assertValidNode(self)
|
1180
|
+
_setTailText(self._c_node, value)
|
1181
|
+
|
1182
|
+
# using 'del el.tail' is the wrong thing to do
|
1183
|
+
#def __del__(self):
|
1184
|
+
# _setTailText(self._c_node, None)
|
1185
|
+
|
1186
|
+
# not in ElementTree, read-only
|
1187
|
+
@property
|
1188
|
+
def prefix(self):
|
1189
|
+
"""Namespace prefix or None.
|
1190
|
+
"""
|
1191
|
+
if self._c_node.ns is not NULL:
|
1192
|
+
if self._c_node.ns.prefix is not NULL:
|
1193
|
+
return funicode(self._c_node.ns.prefix)
|
1194
|
+
return None
|
1195
|
+
|
1196
|
+
# not in ElementTree, read-only
|
1197
|
+
property sourceline:
|
1198
|
+
"""Original line number as found by the parser or None if unknown.
|
1199
|
+
"""
|
1200
|
+
def __get__(self):
|
1201
|
+
cdef long line
|
1202
|
+
_assertValidNode(self)
|
1203
|
+
line = tree.xmlGetLineNo(self._c_node)
|
1204
|
+
return line if line > 0 else None
|
1205
|
+
|
1206
|
+
def __set__(self, line):
|
1207
|
+
_assertValidNode(self)
|
1208
|
+
if line <= 0:
|
1209
|
+
self._c_node.line = 0
|
1210
|
+
else:
|
1211
|
+
self._c_node.line = line
|
1212
|
+
|
1213
|
+
# not in ElementTree, read-only
|
1214
|
+
@property
|
1215
|
+
def nsmap(self):
|
1216
|
+
"""Namespace prefix->URI mapping known in the context of this
|
1217
|
+
Element. This includes all namespace declarations of the
|
1218
|
+
parents.
|
1219
|
+
|
1220
|
+
Note that changing the returned dict has no effect on the Element.
|
1221
|
+
"""
|
1222
|
+
_assertValidNode(self)
|
1223
|
+
return _build_nsmap(self._c_node)
|
1224
|
+
|
1225
|
+
# not in ElementTree, read-only
|
1226
|
+
property base:
|
1227
|
+
"""The base URI of the Element (xml:base or HTML base URL).
|
1228
|
+
None if the base URI is unknown.
|
1229
|
+
|
1230
|
+
Note that the value depends on the URL of the document that
|
1231
|
+
holds the Element if there is no xml:base attribute on the
|
1232
|
+
Element or its ancestors.
|
1233
|
+
|
1234
|
+
Setting this property will set an xml:base attribute on the
|
1235
|
+
Element, regardless of the document type (XML or HTML).
|
1236
|
+
"""
|
1237
|
+
def __get__(self):
|
1238
|
+
_assertValidNode(self)
|
1239
|
+
c_base = tree.xmlNodeGetBase(self._doc._c_doc, self._c_node)
|
1240
|
+
if c_base is NULL:
|
1241
|
+
if self._doc._c_doc.URL is NULL:
|
1242
|
+
return None
|
1243
|
+
return _decodeFilename(self._doc._c_doc.URL)
|
1244
|
+
try:
|
1245
|
+
base = _decodeFilename(c_base)
|
1246
|
+
finally:
|
1247
|
+
tree.xmlFree(c_base)
|
1248
|
+
return base
|
1249
|
+
|
1250
|
+
def __set__(self, url):
|
1251
|
+
_assertValidNode(self)
|
1252
|
+
if url is None:
|
1253
|
+
c_base = <const_xmlChar*>NULL
|
1254
|
+
else:
|
1255
|
+
url = _encodeFilename(url)
|
1256
|
+
c_base = _xcstr(url)
|
1257
|
+
tree.xmlNodeSetBase(self._c_node, c_base)
|
1258
|
+
|
1259
|
+
# ACCESSORS
|
1260
|
+
def __repr__(self):
|
1261
|
+
"__repr__(self)"
|
1262
|
+
return "<Element %s at 0x%x>" % (self.tag, id(self))
|
1263
|
+
|
1264
|
+
def __getitem__(self, x):
|
1265
|
+
"""Returns the subelement at the given position or the requested
|
1266
|
+
slice.
|
1267
|
+
"""
|
1268
|
+
cdef xmlNode* c_node = NULL
|
1269
|
+
cdef Py_ssize_t step = 0, slicelength = 0
|
1270
|
+
cdef Py_ssize_t c, i
|
1271
|
+
cdef _node_to_node_function next_element
|
1272
|
+
cdef list result
|
1273
|
+
_assertValidNode(self)
|
1274
|
+
if isinstance(x, slice):
|
1275
|
+
# slicing
|
1276
|
+
if _isFullSlice(<slice>x):
|
1277
|
+
return _collectChildren(self)
|
1278
|
+
_findChildSlice(<slice>x, self._c_node, &c_node, &step, &slicelength)
|
1279
|
+
if c_node is NULL:
|
1280
|
+
return []
|
1281
|
+
if step > 0:
|
1282
|
+
next_element = _nextElement
|
1283
|
+
else:
|
1284
|
+
step = -step
|
1285
|
+
next_element = _previousElement
|
1286
|
+
result = []
|
1287
|
+
c = 0
|
1288
|
+
while c_node is not NULL and c < slicelength:
|
1289
|
+
result.append(_elementFactory(self._doc, c_node))
|
1290
|
+
c += 1
|
1291
|
+
for i in range(step):
|
1292
|
+
c_node = next_element(c_node)
|
1293
|
+
if c_node is NULL:
|
1294
|
+
break
|
1295
|
+
return result
|
1296
|
+
else:
|
1297
|
+
# indexing
|
1298
|
+
c_node = _findChild(self._c_node, x)
|
1299
|
+
if c_node is NULL:
|
1300
|
+
raise IndexError, "list index out of range"
|
1301
|
+
return _elementFactory(self._doc, c_node)
|
1302
|
+
|
1303
|
+
def __len__(self):
|
1304
|
+
"""__len__(self)
|
1305
|
+
|
1306
|
+
Returns the number of subelements.
|
1307
|
+
"""
|
1308
|
+
_assertValidNode(self)
|
1309
|
+
return _countElements(self._c_node.children)
|
1310
|
+
|
1311
|
+
def __bool__(self):
|
1312
|
+
"""__bool__(self)"""
|
1313
|
+
import warnings
|
1314
|
+
warnings.warn(
|
1315
|
+
"Truth-testing of elements was a source of confusion and will always "
|
1316
|
+
"return True in future versions. "
|
1317
|
+
"Use specific 'len(elem)' or 'elem is not None' test instead.",
|
1318
|
+
FutureWarning
|
1319
|
+
)
|
1320
|
+
# emulate old behaviour
|
1321
|
+
_assertValidNode(self)
|
1322
|
+
return _hasChild(self._c_node)
|
1323
|
+
|
1324
|
+
def __contains__(self, element):
|
1325
|
+
"__contains__(self, element)"
|
1326
|
+
cdef xmlNode* c_node
|
1327
|
+
_assertValidNode(self)
|
1328
|
+
if not isinstance(element, _Element):
|
1329
|
+
return 0
|
1330
|
+
c_node = (<_Element>element)._c_node
|
1331
|
+
return c_node is not NULL and c_node.parent is self._c_node
|
1332
|
+
|
1333
|
+
def __iter__(self):
|
1334
|
+
"__iter__(self)"
|
1335
|
+
return ElementChildIterator(self)
|
1336
|
+
|
1337
|
+
def __reversed__(self):
|
1338
|
+
"__reversed__(self)"
|
1339
|
+
return ElementChildIterator(self, reversed=True)
|
1340
|
+
|
1341
|
+
def index(self, child: _Element, start: int = None, stop: int = None):
|
1342
|
+
"""index(self, child, start=None, stop=None)
|
1343
|
+
|
1344
|
+
Find the position of the child within the parent.
|
1345
|
+
|
1346
|
+
This method is not part of the original ElementTree API.
|
1347
|
+
"""
|
1348
|
+
cdef Py_ssize_t k, l
|
1349
|
+
cdef Py_ssize_t c_start, c_stop
|
1350
|
+
cdef xmlNode* c_child
|
1351
|
+
cdef xmlNode* c_start_node
|
1352
|
+
_assertValidNode(self)
|
1353
|
+
_assertValidNode(child)
|
1354
|
+
c_child = child._c_node
|
1355
|
+
if c_child.parent is not self._c_node:
|
1356
|
+
raise ValueError, "Element is not a child of this node."
|
1357
|
+
|
1358
|
+
# handle the unbounded search straight away (normal case)
|
1359
|
+
if stop is None and (start is None or start == 0):
|
1360
|
+
k = 0
|
1361
|
+
c_child = c_child.prev
|
1362
|
+
while c_child is not NULL:
|
1363
|
+
if _isElement(c_child):
|
1364
|
+
k += 1
|
1365
|
+
c_child = c_child.prev
|
1366
|
+
return k
|
1367
|
+
|
1368
|
+
# check indices
|
1369
|
+
if start is None:
|
1370
|
+
c_start = 0
|
1371
|
+
else:
|
1372
|
+
c_start = start
|
1373
|
+
if stop is None:
|
1374
|
+
c_stop = 0
|
1375
|
+
else:
|
1376
|
+
c_stop = stop
|
1377
|
+
if c_stop == 0 or \
|
1378
|
+
c_start >= c_stop and (c_stop > 0 or c_start < 0):
|
1379
|
+
raise ValueError, "list.index(x): x not in slice"
|
1380
|
+
|
1381
|
+
# for negative slice indices, check slice before searching index
|
1382
|
+
if c_start < 0 or c_stop < 0:
|
1383
|
+
# start from right, at most up to leftmost(c_start, c_stop)
|
1384
|
+
if c_start < c_stop:
|
1385
|
+
k = -c_start
|
1386
|
+
else:
|
1387
|
+
k = -c_stop
|
1388
|
+
c_start_node = self._c_node.last
|
1389
|
+
l = 1
|
1390
|
+
while c_start_node != c_child and l < k:
|
1391
|
+
if _isElement(c_start_node):
|
1392
|
+
l += 1
|
1393
|
+
c_start_node = c_start_node.prev
|
1394
|
+
if c_start_node == c_child:
|
1395
|
+
# found! before slice end?
|
1396
|
+
if c_stop < 0 and l <= -c_stop:
|
1397
|
+
raise ValueError, "list.index(x): x not in slice"
|
1398
|
+
elif c_start < 0:
|
1399
|
+
raise ValueError, "list.index(x): x not in slice"
|
1400
|
+
|
1401
|
+
# now determine the index backwards from child
|
1402
|
+
c_child = c_child.prev
|
1403
|
+
k = 0
|
1404
|
+
if c_stop > 0:
|
1405
|
+
# we can optimize: stop after c_stop elements if not found
|
1406
|
+
while c_child != NULL and k < c_stop:
|
1407
|
+
if _isElement(c_child):
|
1408
|
+
k += 1
|
1409
|
+
c_child = c_child.prev
|
1410
|
+
if k < c_stop:
|
1411
|
+
return k
|
1412
|
+
else:
|
1413
|
+
# traverse all
|
1414
|
+
while c_child != NULL:
|
1415
|
+
if _isElement(c_child):
|
1416
|
+
k = k + 1
|
1417
|
+
c_child = c_child.prev
|
1418
|
+
if c_start > 0:
|
1419
|
+
if k >= c_start:
|
1420
|
+
return k
|
1421
|
+
else:
|
1422
|
+
return k
|
1423
|
+
if c_start != 0 or c_stop != 0:
|
1424
|
+
raise ValueError, "list.index(x): x not in slice"
|
1425
|
+
else:
|
1426
|
+
raise ValueError, "list.index(x): x not in list"
|
1427
|
+
|
1428
|
+
def get(self, key, default=None):
|
1429
|
+
"""get(self, key, default=None)
|
1430
|
+
|
1431
|
+
Gets an element attribute.
|
1432
|
+
"""
|
1433
|
+
_assertValidNode(self)
|
1434
|
+
return _getAttributeValue(self, key, default)
|
1435
|
+
|
1436
|
+
def keys(self):
|
1437
|
+
"""keys(self)
|
1438
|
+
|
1439
|
+
Gets a list of attribute names. The names are returned in an
|
1440
|
+
arbitrary order (just like for an ordinary Python dictionary).
|
1441
|
+
"""
|
1442
|
+
_assertValidNode(self)
|
1443
|
+
return _collectAttributes(self._c_node, 1)
|
1444
|
+
|
1445
|
+
def values(self):
|
1446
|
+
"""values(self)
|
1447
|
+
|
1448
|
+
Gets element attribute values as a sequence of strings. The
|
1449
|
+
attributes are returned in an arbitrary order.
|
1450
|
+
"""
|
1451
|
+
_assertValidNode(self)
|
1452
|
+
return _collectAttributes(self._c_node, 2)
|
1453
|
+
|
1454
|
+
def items(self):
|
1455
|
+
"""items(self)
|
1456
|
+
|
1457
|
+
Gets element attributes, as a sequence. The attributes are returned in
|
1458
|
+
an arbitrary order.
|
1459
|
+
"""
|
1460
|
+
_assertValidNode(self)
|
1461
|
+
return _collectAttributes(self._c_node, 3)
|
1462
|
+
|
1463
|
+
def getchildren(self):
|
1464
|
+
"""getchildren(self)
|
1465
|
+
|
1466
|
+
Returns all direct children. The elements are returned in document
|
1467
|
+
order.
|
1468
|
+
|
1469
|
+
:deprecated: Note that this method has been deprecated as of
|
1470
|
+
ElementTree 1.3 and lxml 2.0. New code should use
|
1471
|
+
``list(element)`` or simply iterate over elements.
|
1472
|
+
"""
|
1473
|
+
_assertValidNode(self)
|
1474
|
+
return _collectChildren(self)
|
1475
|
+
|
1476
|
+
def getparent(self):
|
1477
|
+
"""getparent(self)
|
1478
|
+
|
1479
|
+
Returns the parent of this element or None for the root element.
|
1480
|
+
"""
|
1481
|
+
cdef xmlNode* c_node
|
1482
|
+
#_assertValidNode(self) # not needed
|
1483
|
+
c_node = _parentElement(self._c_node)
|
1484
|
+
if c_node is NULL:
|
1485
|
+
return None
|
1486
|
+
return _elementFactory(self._doc, c_node)
|
1487
|
+
|
1488
|
+
def getnext(self):
|
1489
|
+
"""getnext(self)
|
1490
|
+
|
1491
|
+
Returns the following sibling of this element or None.
|
1492
|
+
"""
|
1493
|
+
cdef xmlNode* c_node
|
1494
|
+
#_assertValidNode(self) # not needed
|
1495
|
+
c_node = _nextElement(self._c_node)
|
1496
|
+
if c_node is NULL:
|
1497
|
+
return None
|
1498
|
+
return _elementFactory(self._doc, c_node)
|
1499
|
+
|
1500
|
+
def getprevious(self):
|
1501
|
+
"""getprevious(self)
|
1502
|
+
|
1503
|
+
Returns the preceding sibling of this element or None.
|
1504
|
+
"""
|
1505
|
+
cdef xmlNode* c_node
|
1506
|
+
#_assertValidNode(self) # not needed
|
1507
|
+
c_node = _previousElement(self._c_node)
|
1508
|
+
if c_node is NULL:
|
1509
|
+
return None
|
1510
|
+
return _elementFactory(self._doc, c_node)
|
1511
|
+
|
1512
|
+
def itersiblings(self, tag=None, *tags, preceding=False):
|
1513
|
+
"""itersiblings(self, tag=None, *tags, preceding=False)
|
1514
|
+
|
1515
|
+
Iterate over the following or preceding siblings of this element.
|
1516
|
+
|
1517
|
+
The direction is determined by the 'preceding' keyword which
|
1518
|
+
defaults to False, i.e. forward iteration over the following
|
1519
|
+
siblings. When True, the iterator yields the preceding
|
1520
|
+
siblings in reverse document order, i.e. starting right before
|
1521
|
+
the current element and going backwards.
|
1522
|
+
|
1523
|
+
Can be restricted to find only elements with specific tags,
|
1524
|
+
see `iter`.
|
1525
|
+
"""
|
1526
|
+
if preceding:
|
1527
|
+
if self._c_node and not self._c_node.prev:
|
1528
|
+
return ITER_EMPTY
|
1529
|
+
elif self._c_node and not self._c_node.next:
|
1530
|
+
return ITER_EMPTY
|
1531
|
+
if tag is not None:
|
1532
|
+
tags += (tag,)
|
1533
|
+
return SiblingsIterator(self, tags, preceding=preceding)
|
1534
|
+
|
1535
|
+
def iterancestors(self, tag=None, *tags):
|
1536
|
+
"""iterancestors(self, tag=None, *tags)
|
1537
|
+
|
1538
|
+
Iterate over the ancestors of this element (from parent to parent).
|
1539
|
+
|
1540
|
+
Can be restricted to find only elements with specific tags,
|
1541
|
+
see `iter`.
|
1542
|
+
"""
|
1543
|
+
if self._c_node and not self._c_node.parent:
|
1544
|
+
return ITER_EMPTY
|
1545
|
+
if tag is not None:
|
1546
|
+
tags += (tag,)
|
1547
|
+
return AncestorsIterator(self, tags)
|
1548
|
+
|
1549
|
+
def iterdescendants(self, tag=None, *tags):
|
1550
|
+
"""iterdescendants(self, tag=None, *tags)
|
1551
|
+
|
1552
|
+
Iterate over the descendants of this element in document order.
|
1553
|
+
|
1554
|
+
As opposed to ``el.iter()``, this iterator does not yield the element
|
1555
|
+
itself. The returned elements can be restricted to find only elements
|
1556
|
+
with specific tags, see `iter`.
|
1557
|
+
"""
|
1558
|
+
if self._c_node and not self._c_node.children:
|
1559
|
+
return ITER_EMPTY
|
1560
|
+
if tag is not None:
|
1561
|
+
tags += (tag,)
|
1562
|
+
return ElementDepthFirstIterator(self, tags, inclusive=False)
|
1563
|
+
|
1564
|
+
def iterchildren(self, tag=None, *tags, reversed=False):
|
1565
|
+
"""iterchildren(self, tag=None, *tags, reversed=False)
|
1566
|
+
|
1567
|
+
Iterate over the children of this element.
|
1568
|
+
|
1569
|
+
As opposed to using normal iteration on this element, the returned
|
1570
|
+
elements can be reversed with the 'reversed' keyword and restricted
|
1571
|
+
to find only elements with specific tags, see `iter`.
|
1572
|
+
"""
|
1573
|
+
if self._c_node and not self._c_node.children:
|
1574
|
+
return ITER_EMPTY
|
1575
|
+
if tag is not None:
|
1576
|
+
tags += (tag,)
|
1577
|
+
return ElementChildIterator(self, tags, reversed=reversed)
|
1578
|
+
|
1579
|
+
def getroottree(self):
|
1580
|
+
"""getroottree(self)
|
1581
|
+
|
1582
|
+
Return an ElementTree for the root node of the document that
|
1583
|
+
contains this element.
|
1584
|
+
|
1585
|
+
This is the same as following element.getparent() up the tree until it
|
1586
|
+
returns None (for the root element) and then build an ElementTree for
|
1587
|
+
the last parent that was returned."""
|
1588
|
+
_assertValidDoc(self._doc)
|
1589
|
+
return _elementTreeFactory(self._doc, None)
|
1590
|
+
|
1591
|
+
def getiterator(self, tag=None, *tags):
|
1592
|
+
"""getiterator(self, tag=None, *tags)
|
1593
|
+
|
1594
|
+
Returns a sequence or iterator of all elements in the subtree in
|
1595
|
+
document order (depth first pre-order), starting with this
|
1596
|
+
element.
|
1597
|
+
|
1598
|
+
Can be restricted to find only elements with specific tags,
|
1599
|
+
see `iter`.
|
1600
|
+
|
1601
|
+
:deprecated: Note that this method is deprecated as of
|
1602
|
+
ElementTree 1.3 and lxml 2.0. It returns an iterator in
|
1603
|
+
lxml, which diverges from the original ElementTree
|
1604
|
+
behaviour. If you want an efficient iterator, use the
|
1605
|
+
``element.iter()`` method instead. You should only use this
|
1606
|
+
method in new code if you require backwards compatibility
|
1607
|
+
with older versions of lxml or ElementTree.
|
1608
|
+
"""
|
1609
|
+
if tag is not None:
|
1610
|
+
tags += (tag,)
|
1611
|
+
return ElementDepthFirstIterator(self, tags)
|
1612
|
+
|
1613
|
+
def iter(self, tag=None, *tags):
|
1614
|
+
"""iter(self, tag=None, *tags)
|
1615
|
+
|
1616
|
+
Iterate over all elements in the subtree in document order (depth
|
1617
|
+
first pre-order), starting with this element.
|
1618
|
+
|
1619
|
+
Can be restricted to find only elements with specific tags:
|
1620
|
+
pass ``"{ns}localname"`` as tag. Either or both of ``ns`` and
|
1621
|
+
``localname`` can be ``*`` for a wildcard; ``ns`` can be empty
|
1622
|
+
for no namespace. ``"localname"`` is equivalent to ``"{}localname"``
|
1623
|
+
(i.e. no namespace) but ``"*"`` is ``"{*}*"`` (any or no namespace),
|
1624
|
+
not ``"{}*"``.
|
1625
|
+
|
1626
|
+
You can also pass the Element, Comment, ProcessingInstruction and
|
1627
|
+
Entity factory functions to look only for the specific element type.
|
1628
|
+
|
1629
|
+
Passing multiple tags (or a sequence of tags) instead of a single tag
|
1630
|
+
will let the iterator return all elements matching any of these tags,
|
1631
|
+
in document order.
|
1632
|
+
"""
|
1633
|
+
if tag is not None:
|
1634
|
+
tags += (tag,)
|
1635
|
+
return ElementDepthFirstIterator(self, tags)
|
1636
|
+
|
1637
|
+
def itertext(self, tag=None, *tags, with_tail=True):
|
1638
|
+
"""itertext(self, tag=None, *tags, with_tail=True)
|
1639
|
+
|
1640
|
+
Iterates over the text content of a subtree.
|
1641
|
+
|
1642
|
+
You can pass tag names to restrict text content to specific elements,
|
1643
|
+
see `iter`.
|
1644
|
+
|
1645
|
+
You can set the ``with_tail`` keyword argument to ``False`` to skip
|
1646
|
+
over tail text.
|
1647
|
+
"""
|
1648
|
+
if tag is not None:
|
1649
|
+
tags += (tag,)
|
1650
|
+
return ElementTextIterator(self, tags, with_tail=with_tail)
|
1651
|
+
|
1652
|
+
def makeelement(self, _tag, attrib=None, nsmap=None, **_extra):
|
1653
|
+
"""makeelement(self, _tag, attrib=None, nsmap=None, **_extra)
|
1654
|
+
|
1655
|
+
Creates a new element associated with the same document.
|
1656
|
+
"""
|
1657
|
+
_assertValidDoc(self._doc)
|
1658
|
+
return _makeElement(_tag, NULL, self._doc, None, None, None,
|
1659
|
+
attrib, nsmap, _extra)
|
1660
|
+
|
1661
|
+
def find(self, path, namespaces=None):
|
1662
|
+
"""find(self, path, namespaces=None)
|
1663
|
+
|
1664
|
+
Finds the first matching subelement, by tag name or path.
|
1665
|
+
|
1666
|
+
The optional ``namespaces`` argument accepts a
|
1667
|
+
prefix-to-namespace mapping that allows the usage of XPath
|
1668
|
+
prefixes in the path expression.
|
1669
|
+
"""
|
1670
|
+
if isinstance(path, QName):
|
1671
|
+
path = (<QName>path).text
|
1672
|
+
return _elementpath.find(self, path, namespaces, with_prefixes=not _isHtmlDocument(self))
|
1673
|
+
|
1674
|
+
def findtext(self, path, default=None, namespaces=None):
|
1675
|
+
"""findtext(self, path, default=None, namespaces=None)
|
1676
|
+
|
1677
|
+
Finds text for the first matching subelement, by tag name or path.
|
1678
|
+
|
1679
|
+
The optional ``namespaces`` argument accepts a
|
1680
|
+
prefix-to-namespace mapping that allows the usage of XPath
|
1681
|
+
prefixes in the path expression.
|
1682
|
+
"""
|
1683
|
+
if isinstance(path, QName):
|
1684
|
+
path = (<QName>path).text
|
1685
|
+
return _elementpath.findtext(self, path, default, namespaces, with_prefixes=not _isHtmlDocument(self))
|
1686
|
+
|
1687
|
+
def findall(self, path, namespaces=None):
|
1688
|
+
"""findall(self, path, namespaces=None)
|
1689
|
+
|
1690
|
+
Finds all matching subelements, by tag name or path.
|
1691
|
+
|
1692
|
+
The optional ``namespaces`` argument accepts a
|
1693
|
+
prefix-to-namespace mapping that allows the usage of XPath
|
1694
|
+
prefixes in the path expression.
|
1695
|
+
"""
|
1696
|
+
if isinstance(path, QName):
|
1697
|
+
path = (<QName>path).text
|
1698
|
+
return _elementpath.findall(self, path, namespaces, with_prefixes=not _isHtmlDocument(self))
|
1699
|
+
|
1700
|
+
def iterfind(self, path, namespaces=None):
|
1701
|
+
"""iterfind(self, path, namespaces=None)
|
1702
|
+
|
1703
|
+
Iterates over all matching subelements, by tag name or path.
|
1704
|
+
|
1705
|
+
The optional ``namespaces`` argument accepts a
|
1706
|
+
prefix-to-namespace mapping that allows the usage of XPath
|
1707
|
+
prefixes in the path expression.
|
1708
|
+
"""
|
1709
|
+
if isinstance(path, QName):
|
1710
|
+
path = (<QName>path).text
|
1711
|
+
return _elementpath.iterfind(self, path, namespaces, with_prefixes=not _isHtmlDocument(self))
|
1712
|
+
|
1713
|
+
def xpath(self, _path, *, namespaces=None, extensions=None,
|
1714
|
+
smart_strings=True, **_variables):
|
1715
|
+
"""xpath(self, _path, namespaces=None, extensions=None, smart_strings=True, **_variables)
|
1716
|
+
|
1717
|
+
Evaluate an xpath expression using the element as context node.
|
1718
|
+
"""
|
1719
|
+
evaluator = XPathElementEvaluator(self, namespaces=namespaces,
|
1720
|
+
extensions=extensions,
|
1721
|
+
smart_strings=smart_strings)
|
1722
|
+
return evaluator(_path, **_variables)
|
1723
|
+
|
1724
|
+
def cssselect(self, expr, *, translator='xml'):
|
1725
|
+
"""
|
1726
|
+
Run the CSS expression on this element and its children,
|
1727
|
+
returning a list of the results.
|
1728
|
+
|
1729
|
+
Equivalent to lxml.cssselect.CSSSelect(expr)(self) -- note
|
1730
|
+
that pre-compiling the expression can provide a substantial
|
1731
|
+
speedup.
|
1732
|
+
"""
|
1733
|
+
# Do the import here to make the dependency optional.
|
1734
|
+
from lxml.cssselect import CSSSelector
|
1735
|
+
return CSSSelector(expr, translator=translator)(self)
|
1736
|
+
|
1737
|
+
|
1738
|
+
@cython.linetrace(False)
|
1739
|
+
cdef _Element _elementFactory(_Document doc, xmlNode* c_node):
|
1740
|
+
cdef _Element result
|
1741
|
+
result = getProxy(c_node)
|
1742
|
+
if result is not None:
|
1743
|
+
return result
|
1744
|
+
if c_node is NULL:
|
1745
|
+
return None
|
1746
|
+
|
1747
|
+
element_class = <type> LOOKUP_ELEMENT_CLASS(
|
1748
|
+
ELEMENT_CLASS_LOOKUP_STATE, doc, c_node)
|
1749
|
+
if type(element_class) is not type:
|
1750
|
+
if not isinstance(element_class, type):
|
1751
|
+
raise TypeError(f"Element class is not a type, got {type(element_class)}")
|
1752
|
+
if hasProxy(c_node):
|
1753
|
+
# prevent re-entry race condition - we just called into Python
|
1754
|
+
return getProxy(c_node)
|
1755
|
+
result = element_class.__new__(element_class)
|
1756
|
+
if hasProxy(c_node):
|
1757
|
+
# prevent re-entry race condition - we just called into Python
|
1758
|
+
result._c_node = NULL
|
1759
|
+
return getProxy(c_node)
|
1760
|
+
|
1761
|
+
_registerProxy(result, doc, c_node)
|
1762
|
+
if element_class is not _Element:
|
1763
|
+
result._init()
|
1764
|
+
return result
|
1765
|
+
|
1766
|
+
|
1767
|
+
@cython.internal
|
1768
|
+
cdef class __ContentOnlyElement(_Element):
|
1769
|
+
cdef int _raiseImmutable(self) except -1:
|
1770
|
+
raise TypeError, "this element does not have children or attributes"
|
1771
|
+
|
1772
|
+
def set(self, key, value):
|
1773
|
+
"set(self, key, value)"
|
1774
|
+
self._raiseImmutable()
|
1775
|
+
|
1776
|
+
def append(self, value):
|
1777
|
+
"append(self, value)"
|
1778
|
+
self._raiseImmutable()
|
1779
|
+
|
1780
|
+
def insert(self, index, value):
|
1781
|
+
"insert(self, index, value)"
|
1782
|
+
self._raiseImmutable()
|
1783
|
+
|
1784
|
+
def __setitem__(self, index, value):
|
1785
|
+
"__setitem__(self, index, value)"
|
1786
|
+
self._raiseImmutable()
|
1787
|
+
|
1788
|
+
@property
|
1789
|
+
def attrib(self):
|
1790
|
+
return IMMUTABLE_EMPTY_MAPPING
|
1791
|
+
|
1792
|
+
property text:
|
1793
|
+
def __get__(self):
|
1794
|
+
_assertValidNode(self)
|
1795
|
+
return funicodeOrEmpty(self._c_node.content)
|
1796
|
+
|
1797
|
+
def __set__(self, value):
|
1798
|
+
cdef tree.xmlDict* c_dict
|
1799
|
+
_assertValidNode(self)
|
1800
|
+
if value is None:
|
1801
|
+
c_text = <const_xmlChar*>NULL
|
1802
|
+
else:
|
1803
|
+
value = _utf8(value)
|
1804
|
+
c_text = _xcstr(value)
|
1805
|
+
tree.xmlNodeSetContent(self._c_node, c_text)
|
1806
|
+
|
1807
|
+
# ACCESSORS
|
1808
|
+
def __getitem__(self, x):
|
1809
|
+
"__getitem__(self, x)"
|
1810
|
+
if isinstance(x, slice):
|
1811
|
+
return []
|
1812
|
+
else:
|
1813
|
+
raise IndexError, "list index out of range"
|
1814
|
+
|
1815
|
+
def __len__(self):
|
1816
|
+
"__len__(self)"
|
1817
|
+
return 0
|
1818
|
+
|
1819
|
+
def get(self, key, default=None):
|
1820
|
+
"get(self, key, default=None)"
|
1821
|
+
return None
|
1822
|
+
|
1823
|
+
def keys(self):
|
1824
|
+
"keys(self)"
|
1825
|
+
return []
|
1826
|
+
|
1827
|
+
def items(self):
|
1828
|
+
"items(self)"
|
1829
|
+
return []
|
1830
|
+
|
1831
|
+
def values(self):
|
1832
|
+
"values(self)"
|
1833
|
+
return []
|
1834
|
+
|
1835
|
+
cdef class _Comment(__ContentOnlyElement):
|
1836
|
+
@property
|
1837
|
+
def tag(self):
|
1838
|
+
return Comment
|
1839
|
+
|
1840
|
+
def __repr__(self):
|
1841
|
+
return "<!--%s-->" % self.text
|
1842
|
+
|
1843
|
+
cdef class _ProcessingInstruction(__ContentOnlyElement):
|
1844
|
+
@property
|
1845
|
+
def tag(self):
|
1846
|
+
return ProcessingInstruction
|
1847
|
+
|
1848
|
+
property target:
|
1849
|
+
# not in ElementTree
|
1850
|
+
def __get__(self):
|
1851
|
+
_assertValidNode(self)
|
1852
|
+
return funicode(self._c_node.name)
|
1853
|
+
|
1854
|
+
def __set__(self, value):
|
1855
|
+
_assertValidNode(self)
|
1856
|
+
value = _utf8(value)
|
1857
|
+
c_text = _xcstr(value)
|
1858
|
+
tree.xmlNodeSetName(self._c_node, c_text)
|
1859
|
+
|
1860
|
+
def __repr__(self):
|
1861
|
+
text = self.text
|
1862
|
+
if text:
|
1863
|
+
return "<?%s %s?>" % (self.target, text)
|
1864
|
+
else:
|
1865
|
+
return "<?%s?>" % self.target
|
1866
|
+
|
1867
|
+
def get(self, key, default=None):
|
1868
|
+
"""get(self, key, default=None)
|
1869
|
+
|
1870
|
+
Try to parse pseudo-attributes from the text content of the
|
1871
|
+
processing instruction, search for one with the given key as
|
1872
|
+
name and return its associated value.
|
1873
|
+
|
1874
|
+
Note that this is only a convenience method for the most
|
1875
|
+
common case that all text content is structured in
|
1876
|
+
attribute-like name-value pairs with properly quoted values.
|
1877
|
+
It is not guaranteed to work for all possible text content.
|
1878
|
+
"""
|
1879
|
+
return self.attrib.get(key, default)
|
1880
|
+
|
1881
|
+
@property
|
1882
|
+
def attrib(self):
|
1883
|
+
"""Returns a dict containing all pseudo-attributes that can be
|
1884
|
+
parsed from the text content of this processing instruction.
|
1885
|
+
Note that modifying the dict currently has no effect on the
|
1886
|
+
XML node, although this is not guaranteed to stay this way.
|
1887
|
+
"""
|
1888
|
+
return { attr : (value1 or value2)
|
1889
|
+
for attr, value1, value2 in _FIND_PI_ATTRIBUTES(' ' + self.text) }
|
1890
|
+
|
1891
|
+
cdef object _FIND_PI_ATTRIBUTES = re.compile(r'\s+(\w+)\s*=\s*(?:\'([^\']*)\'|"([^"]*)")', re.U).findall
|
1892
|
+
|
1893
|
+
cdef class _Entity(__ContentOnlyElement):
|
1894
|
+
@property
|
1895
|
+
def tag(self):
|
1896
|
+
return Entity
|
1897
|
+
|
1898
|
+
property name:
|
1899
|
+
# not in ElementTree
|
1900
|
+
def __get__(self):
|
1901
|
+
_assertValidNode(self)
|
1902
|
+
return funicode(self._c_node.name)
|
1903
|
+
|
1904
|
+
def __set__(self, value):
|
1905
|
+
_assertValidNode(self)
|
1906
|
+
value_utf = _utf8(value)
|
1907
|
+
if b'&' in value_utf or b';' in value_utf:
|
1908
|
+
raise ValueError, f"Invalid entity name '{value}'"
|
1909
|
+
tree.xmlNodeSetName(self._c_node, _xcstr(value_utf))
|
1910
|
+
|
1911
|
+
@property
|
1912
|
+
def text(self):
|
1913
|
+
# FIXME: should this be None or '&[VALUE];' or the resolved
|
1914
|
+
# entity value ?
|
1915
|
+
_assertValidNode(self)
|
1916
|
+
return f'&{funicode(self._c_node.name)};'
|
1917
|
+
|
1918
|
+
def __repr__(self):
|
1919
|
+
return "&%s;" % self.name
|
1920
|
+
|
1921
|
+
|
1922
|
+
cdef class QName:
|
1923
|
+
"""QName(text_or_uri_or_element, tag=None)
|
1924
|
+
|
1925
|
+
QName wrapper for qualified XML names.
|
1926
|
+
|
1927
|
+
Pass a tag name by itself or a namespace URI and a tag name to
|
1928
|
+
create a qualified name. Alternatively, pass an Element to
|
1929
|
+
extract its tag name. ``None`` as first argument is ignored in
|
1930
|
+
order to allow for generic 2-argument usage.
|
1931
|
+
|
1932
|
+
The ``text`` property holds the qualified name in
|
1933
|
+
``{namespace}tagname`` notation. The ``namespace`` and
|
1934
|
+
``localname`` properties hold the respective parts of the tag
|
1935
|
+
name.
|
1936
|
+
|
1937
|
+
You can pass QName objects wherever a tag name is expected. Also,
|
1938
|
+
setting Element text from a QName will resolve the namespace prefix
|
1939
|
+
on assignment and set a qualified text value. This is helpful in XML
|
1940
|
+
languages like SOAP or XML-Schema that use prefixed tag names in
|
1941
|
+
their text content.
|
1942
|
+
"""
|
1943
|
+
cdef readonly unicode text
|
1944
|
+
cdef readonly unicode localname
|
1945
|
+
cdef readonly unicode namespace
|
1946
|
+
def __init__(self, text_or_uri_or_element, tag=None):
|
1947
|
+
if text_or_uri_or_element is None:
|
1948
|
+
# Allow None as no namespace.
|
1949
|
+
text_or_uri_or_element, tag = tag, None
|
1950
|
+
if not _isString(text_or_uri_or_element):
|
1951
|
+
if isinstance(text_or_uri_or_element, _Element):
|
1952
|
+
text_or_uri_or_element = (<_Element>text_or_uri_or_element).tag
|
1953
|
+
if not _isString(text_or_uri_or_element):
|
1954
|
+
raise ValueError, f"Invalid input tag of type {type(text_or_uri_or_element)!r}"
|
1955
|
+
elif isinstance(text_or_uri_or_element, QName):
|
1956
|
+
text_or_uri_or_element = (<QName>text_or_uri_or_element).text
|
1957
|
+
elif text_or_uri_or_element is not None:
|
1958
|
+
text_or_uri_or_element = unicode(text_or_uri_or_element)
|
1959
|
+
else:
|
1960
|
+
raise ValueError, f"Invalid input tag of type {type(text_or_uri_or_element)!r}"
|
1961
|
+
|
1962
|
+
ns_utf, tag_utf = _getNsTag(text_or_uri_or_element)
|
1963
|
+
if tag is not None:
|
1964
|
+
# either ('ns', 'tag') or ('{ns}oldtag', 'newtag')
|
1965
|
+
if ns_utf is None:
|
1966
|
+
ns_utf = tag_utf # case 1: namespace ended up as tag name
|
1967
|
+
tag_utf = _utf8(tag)
|
1968
|
+
_tagValidOrRaise(tag_utf)
|
1969
|
+
self.localname = (<bytes>tag_utf).decode('utf8')
|
1970
|
+
if ns_utf is None:
|
1971
|
+
self.namespace = None
|
1972
|
+
self.text = self.localname
|
1973
|
+
else:
|
1974
|
+
self.namespace = (<bytes>ns_utf).decode('utf8')
|
1975
|
+
self.text = "{%s}%s" % (self.namespace, self.localname)
|
1976
|
+
def __str__(self):
|
1977
|
+
return self.text
|
1978
|
+
def __hash__(self):
|
1979
|
+
return hash(self.text)
|
1980
|
+
def __richcmp__(self, other, int op):
|
1981
|
+
try:
|
1982
|
+
if type(other) is QName:
|
1983
|
+
other = (<QName>other).text
|
1984
|
+
elif not isinstance(other, unicode):
|
1985
|
+
other = unicode(other)
|
1986
|
+
except (ValueError, UnicodeDecodeError):
|
1987
|
+
return NotImplemented
|
1988
|
+
return python.PyObject_RichCompare(self.text, other, op)
|
1989
|
+
|
1990
|
+
|
1991
|
+
cdef public class _ElementTree [ type LxmlElementTreeType,
|
1992
|
+
object LxmlElementTree ]:
|
1993
|
+
cdef _Document _doc
|
1994
|
+
cdef _Element _context_node
|
1995
|
+
|
1996
|
+
# Note that _doc is only used to store the original document if we do not
|
1997
|
+
# have a _context_node. All methods should prefer self._context_node._doc
|
1998
|
+
# to honour tree restructuring. _doc can happily be None!
|
1999
|
+
|
2000
|
+
@cython.final
|
2001
|
+
cdef int _assertHasRoot(self) except -1:
|
2002
|
+
"""We have to take care here: the document may not have a root node!
|
2003
|
+
This can happen if ElementTree() is called without any argument and
|
2004
|
+
the caller 'forgets' to call parse() afterwards, so this is a bug in
|
2005
|
+
the caller program.
|
2006
|
+
"""
|
2007
|
+
assert self._context_node is not None, \
|
2008
|
+
"ElementTree not initialized, missing root"
|
2009
|
+
return 0
|
2010
|
+
|
2011
|
+
def parse(self, source, _BaseParser parser=None, *, base_url=None):
|
2012
|
+
"""parse(self, source, parser=None, base_url=None)
|
2013
|
+
|
2014
|
+
Updates self with the content of source and returns its root.
|
2015
|
+
"""
|
2016
|
+
cdef _Document doc = None
|
2017
|
+
try:
|
2018
|
+
doc = _parseDocument(source, parser, base_url)
|
2019
|
+
except _TargetParserResult as result_container:
|
2020
|
+
# raises a TypeError if we don't get an _Element
|
2021
|
+
self._context_node = result_container.result
|
2022
|
+
else:
|
2023
|
+
self._context_node = doc.getroot()
|
2024
|
+
self._doc = None if self._context_node is not None else doc
|
2025
|
+
return self._context_node
|
2026
|
+
|
2027
|
+
def _setroot(self, _Element root not None):
|
2028
|
+
"""_setroot(self, root)
|
2029
|
+
|
2030
|
+
Relocate the ElementTree to a new root node.
|
2031
|
+
"""
|
2032
|
+
_assertValidNode(root)
|
2033
|
+
if root._c_node.type != tree.XML_ELEMENT_NODE:
|
2034
|
+
raise TypeError, "Only elements can be the root of an ElementTree"
|
2035
|
+
self._context_node = root
|
2036
|
+
self._doc = None
|
2037
|
+
|
2038
|
+
def getroot(self):
|
2039
|
+
"""getroot(self)
|
2040
|
+
|
2041
|
+
Gets the root element for this tree.
|
2042
|
+
"""
|
2043
|
+
return self._context_node
|
2044
|
+
|
2045
|
+
def __copy__(self):
|
2046
|
+
return _elementTreeFactory(self._doc, self._context_node)
|
2047
|
+
|
2048
|
+
def __deepcopy__(self, memo):
|
2049
|
+
cdef _Element root
|
2050
|
+
cdef _Document doc
|
2051
|
+
cdef xmlDoc* c_doc
|
2052
|
+
if self._context_node is not None:
|
2053
|
+
root = self._context_node.__copy__()
|
2054
|
+
assert root is not None
|
2055
|
+
_assertValidNode(root)
|
2056
|
+
_copyNonElementSiblings(self._context_node._c_node, root._c_node)
|
2057
|
+
return _elementTreeFactory(None, root)
|
2058
|
+
elif self._doc is not None:
|
2059
|
+
_assertValidDoc(self._doc)
|
2060
|
+
c_doc = tree.xmlCopyDoc(self._doc._c_doc, 1)
|
2061
|
+
if c_doc is NULL:
|
2062
|
+
raise MemoryError()
|
2063
|
+
doc = _documentFactory(c_doc, self._doc._parser)
|
2064
|
+
return _elementTreeFactory(doc, None)
|
2065
|
+
else:
|
2066
|
+
# so what ...
|
2067
|
+
return self
|
2068
|
+
|
2069
|
+
# not in ElementTree
|
2070
|
+
@property
|
2071
|
+
def docinfo(self) -> DocInfo:
|
2072
|
+
"""Information about the document provided by parser and DTD."""
|
2073
|
+
self._assertHasRoot()
|
2074
|
+
return DocInfo(self._context_node._doc)
|
2075
|
+
|
2076
|
+
# not in ElementTree, read-only
|
2077
|
+
@property
|
2078
|
+
def parser(self):
|
2079
|
+
"""The parser that was used to parse the document in this ElementTree.
|
2080
|
+
"""
|
2081
|
+
if self._context_node is not None and \
|
2082
|
+
self._context_node._doc is not None:
|
2083
|
+
return self._context_node._doc._parser
|
2084
|
+
if self._doc is not None:
|
2085
|
+
return self._doc._parser
|
2086
|
+
return None
|
2087
|
+
|
2088
|
+
def write(self, file, *, encoding=None, method="xml",
|
2089
|
+
bint pretty_print=False, xml_declaration=None, bint with_tail=True,
|
2090
|
+
standalone=None, doctype=None, compression=0,
|
2091
|
+
bint exclusive=False, inclusive_ns_prefixes=None,
|
2092
|
+
bint with_comments=True, bint strip_text=False,
|
2093
|
+
docstring=None):
|
2094
|
+
"""write(self, file, encoding=None, method="xml",
|
2095
|
+
pretty_print=False, xml_declaration=None, with_tail=True,
|
2096
|
+
standalone=None, doctype=None, compression=0,
|
2097
|
+
exclusive=False, inclusive_ns_prefixes=None,
|
2098
|
+
with_comments=True, strip_text=False)
|
2099
|
+
|
2100
|
+
Write the tree to a filename, file or file-like object.
|
2101
|
+
|
2102
|
+
Defaults to ASCII encoding and writing a declaration as needed.
|
2103
|
+
|
2104
|
+
The keyword argument 'method' selects the output method:
|
2105
|
+
'xml', 'html', 'text', 'c14n' or 'c14n2'. Default is 'xml'.
|
2106
|
+
|
2107
|
+
With ``method="c14n"`` (C14N version 1), the options ``exclusive``,
|
2108
|
+
``with_comments`` and ``inclusive_ns_prefixes`` request exclusive
|
2109
|
+
C14N, include comments, and list the inclusive prefixes respectively.
|
2110
|
+
|
2111
|
+
With ``method="c14n2"`` (C14N version 2), the ``with_comments`` and
|
2112
|
+
``strip_text`` options control the output of comments and text space
|
2113
|
+
according to C14N 2.0.
|
2114
|
+
|
2115
|
+
Passing a boolean value to the ``standalone`` option will
|
2116
|
+
output an XML declaration with the corresponding
|
2117
|
+
``standalone`` flag.
|
2118
|
+
|
2119
|
+
The ``doctype`` option allows passing in a plain string that will
|
2120
|
+
be serialised before the XML tree. Note that passing in non
|
2121
|
+
well-formed content here will make the XML output non well-formed.
|
2122
|
+
Also, an existing doctype in the document tree will not be removed
|
2123
|
+
when serialising an ElementTree instance.
|
2124
|
+
|
2125
|
+
The ``compression`` option enables GZip compression level 1-9.
|
2126
|
+
|
2127
|
+
The ``inclusive_ns_prefixes`` should be a list of namespace strings
|
2128
|
+
(i.e. ['xs', 'xsi']) that will be promoted to the top-level element
|
2129
|
+
during exclusive C14N serialisation. This parameter is ignored if
|
2130
|
+
exclusive mode=False.
|
2131
|
+
|
2132
|
+
If exclusive=True and no list is provided, a namespace will only be
|
2133
|
+
rendered if it is used by the immediate parent or one of its attributes
|
2134
|
+
and its prefix and values have not already been rendered by an ancestor
|
2135
|
+
of the namespace node's parent element.
|
2136
|
+
"""
|
2137
|
+
cdef bint write_declaration
|
2138
|
+
cdef int is_standalone
|
2139
|
+
|
2140
|
+
self._assertHasRoot()
|
2141
|
+
_assertValidNode(self._context_node)
|
2142
|
+
if compression is None or compression < 0:
|
2143
|
+
compression = 0
|
2144
|
+
|
2145
|
+
# C14N serialisation
|
2146
|
+
if method in ('c14n', 'c14n2'):
|
2147
|
+
if encoding is not None:
|
2148
|
+
raise ValueError("Cannot specify encoding with C14N")
|
2149
|
+
if xml_declaration:
|
2150
|
+
raise ValueError("Cannot enable XML declaration in C14N")
|
2151
|
+
|
2152
|
+
if method == 'c14n':
|
2153
|
+
_tofilelikeC14N(file, self._context_node, exclusive, with_comments,
|
2154
|
+
compression, inclusive_ns_prefixes)
|
2155
|
+
else: # c14n2
|
2156
|
+
with _open_utf8_file(file, compression=compression) as f:
|
2157
|
+
target = C14NWriterTarget(
|
2158
|
+
f.write, with_comments=with_comments, strip_text=strip_text)
|
2159
|
+
_tree_to_target(self, target)
|
2160
|
+
return
|
2161
|
+
|
2162
|
+
if not with_comments:
|
2163
|
+
raise ValueError("Can only discard comments in C14N serialisation")
|
2164
|
+
# suppress decl. in default case (purely for ElementTree compatibility)
|
2165
|
+
if xml_declaration is not None:
|
2166
|
+
write_declaration = xml_declaration
|
2167
|
+
if encoding is None:
|
2168
|
+
encoding = 'ASCII'
|
2169
|
+
else:
|
2170
|
+
encoding = encoding.upper()
|
2171
|
+
elif encoding is None:
|
2172
|
+
encoding = 'ASCII'
|
2173
|
+
write_declaration = 0
|
2174
|
+
else:
|
2175
|
+
encoding = encoding.upper()
|
2176
|
+
write_declaration = encoding not in (
|
2177
|
+
'US-ASCII', 'ASCII', 'UTF8', 'UTF-8')
|
2178
|
+
if standalone is None:
|
2179
|
+
is_standalone = -1
|
2180
|
+
elif standalone:
|
2181
|
+
write_declaration = 1
|
2182
|
+
is_standalone = 1
|
2183
|
+
else:
|
2184
|
+
write_declaration = 1
|
2185
|
+
is_standalone = 0
|
2186
|
+
|
2187
|
+
if docstring is not None and doctype is None:
|
2188
|
+
import warnings
|
2189
|
+
warnings.warn(
|
2190
|
+
"The 'docstring' option is deprecated. Use 'doctype' instead.",
|
2191
|
+
DeprecationWarning)
|
2192
|
+
doctype = docstring
|
2193
|
+
|
2194
|
+
_tofilelike(file, self._context_node, encoding, doctype, method,
|
2195
|
+
write_declaration, 1, pretty_print, with_tail,
|
2196
|
+
is_standalone, compression)
|
2197
|
+
|
2198
|
+
def getpath(self, _Element element not None):
|
2199
|
+
"""getpath(self, element)
|
2200
|
+
|
2201
|
+
Returns a structural, absolute XPath expression to find the element.
|
2202
|
+
|
2203
|
+
For namespaced elements, the expression uses prefixes from the
|
2204
|
+
document, which therefore need to be provided in order to make any
|
2205
|
+
use of the expression in XPath.
|
2206
|
+
|
2207
|
+
Also see the method getelementpath(self, element), which returns a
|
2208
|
+
self-contained ElementPath expression.
|
2209
|
+
"""
|
2210
|
+
cdef _Document doc
|
2211
|
+
cdef _Element root
|
2212
|
+
cdef xmlDoc* c_doc
|
2213
|
+
_assertValidNode(element)
|
2214
|
+
if self._context_node is not None:
|
2215
|
+
root = self._context_node
|
2216
|
+
doc = root._doc
|
2217
|
+
elif self._doc is not None:
|
2218
|
+
doc = self._doc
|
2219
|
+
root = doc.getroot()
|
2220
|
+
else:
|
2221
|
+
raise ValueError, "Element is not in this tree."
|
2222
|
+
_assertValidDoc(doc)
|
2223
|
+
_assertValidNode(root)
|
2224
|
+
if element._doc is not doc:
|
2225
|
+
raise ValueError, "Element is not in this tree."
|
2226
|
+
|
2227
|
+
c_doc = _fakeRootDoc(doc._c_doc, root._c_node)
|
2228
|
+
c_path = tree.xmlGetNodePath(element._c_node)
|
2229
|
+
_destroyFakeDoc(doc._c_doc, c_doc)
|
2230
|
+
if c_path is NULL:
|
2231
|
+
raise MemoryError()
|
2232
|
+
path = funicode(c_path)
|
2233
|
+
tree.xmlFree(c_path)
|
2234
|
+
return path
|
2235
|
+
|
2236
|
+
def getelementpath(self, _Element element not None):
|
2237
|
+
"""getelementpath(self, element)
|
2238
|
+
|
2239
|
+
Returns a structural, absolute ElementPath expression to find the
|
2240
|
+
element. This path can be used in the .find() method to look up
|
2241
|
+
the element, provided that the elements along the path and their
|
2242
|
+
list of immediate children were not modified in between.
|
2243
|
+
|
2244
|
+
ElementPath has the advantage over an XPath expression (as returned
|
2245
|
+
by the .getpath() method) that it does not require additional prefix
|
2246
|
+
declarations. It is always self-contained.
|
2247
|
+
"""
|
2248
|
+
cdef _Element root
|
2249
|
+
cdef Py_ssize_t count
|
2250
|
+
_assertValidNode(element)
|
2251
|
+
if element._c_node.type != tree.XML_ELEMENT_NODE:
|
2252
|
+
raise ValueError, "input is not an Element"
|
2253
|
+
if self._context_node is not None:
|
2254
|
+
root = self._context_node
|
2255
|
+
elif self._doc is not None:
|
2256
|
+
root = self._doc.getroot()
|
2257
|
+
else:
|
2258
|
+
raise ValueError, "Element is not in this tree"
|
2259
|
+
_assertValidNode(root)
|
2260
|
+
if element._doc is not root._doc:
|
2261
|
+
raise ValueError, "Element is not in this tree"
|
2262
|
+
|
2263
|
+
path = []
|
2264
|
+
c_element = element._c_node
|
2265
|
+
while c_element is not root._c_node:
|
2266
|
+
c_name = c_element.name
|
2267
|
+
c_href = _getNs(c_element)
|
2268
|
+
tag = _namespacedNameFromNsName(c_href, c_name)
|
2269
|
+
if c_href is NULL:
|
2270
|
+
c_href = <const_xmlChar*>b'' # no namespace (NULL is wildcard)
|
2271
|
+
# use tag[N] if there are preceding siblings with the same tag
|
2272
|
+
count = 0
|
2273
|
+
c_node = c_element.prev
|
2274
|
+
while c_node is not NULL:
|
2275
|
+
if c_node.type == tree.XML_ELEMENT_NODE:
|
2276
|
+
if _tagMatches(c_node, c_href, c_name):
|
2277
|
+
count += 1
|
2278
|
+
c_node = c_node.prev
|
2279
|
+
if count:
|
2280
|
+
tag = f'{tag}[{count+1}]'
|
2281
|
+
else:
|
2282
|
+
# use tag[1] if there are following siblings with the same tag
|
2283
|
+
c_node = c_element.next
|
2284
|
+
while c_node is not NULL:
|
2285
|
+
if c_node.type == tree.XML_ELEMENT_NODE:
|
2286
|
+
if _tagMatches(c_node, c_href, c_name):
|
2287
|
+
tag += '[1]'
|
2288
|
+
break
|
2289
|
+
c_node = c_node.next
|
2290
|
+
|
2291
|
+
path.append(tag)
|
2292
|
+
c_element = c_element.parent
|
2293
|
+
if c_element is NULL or c_element.type != tree.XML_ELEMENT_NODE:
|
2294
|
+
raise ValueError, "Element is not in this tree."
|
2295
|
+
if not path:
|
2296
|
+
return '.'
|
2297
|
+
path.reverse()
|
2298
|
+
return '/'.join(path)
|
2299
|
+
|
2300
|
+
def getiterator(self, tag=None, *tags):
|
2301
|
+
"""getiterator(self, *tags, tag=None)
|
2302
|
+
|
2303
|
+
Returns a sequence or iterator of all elements in document order
|
2304
|
+
(depth first pre-order), starting with the root element.
|
2305
|
+
|
2306
|
+
Can be restricted to find only elements with specific tags,
|
2307
|
+
see `_Element.iter`.
|
2308
|
+
|
2309
|
+
:deprecated: Note that this method is deprecated as of
|
2310
|
+
ElementTree 1.3 and lxml 2.0. It returns an iterator in
|
2311
|
+
lxml, which diverges from the original ElementTree
|
2312
|
+
behaviour. If you want an efficient iterator, use the
|
2313
|
+
``tree.iter()`` method instead. You should only use this
|
2314
|
+
method in new code if you require backwards compatibility
|
2315
|
+
with older versions of lxml or ElementTree.
|
2316
|
+
"""
|
2317
|
+
root = self.getroot()
|
2318
|
+
if root is None:
|
2319
|
+
return ITER_EMPTY
|
2320
|
+
if tag is not None:
|
2321
|
+
tags += (tag,)
|
2322
|
+
return root.getiterator(*tags)
|
2323
|
+
|
2324
|
+
def iter(self, tag=None, *tags):
|
2325
|
+
"""iter(self, tag=None, *tags)
|
2326
|
+
|
2327
|
+
Creates an iterator for the root element. The iterator loops over
|
2328
|
+
all elements in this tree, in document order. Note that siblings
|
2329
|
+
of the root element (comments or processing instructions) are not
|
2330
|
+
returned by the iterator.
|
2331
|
+
|
2332
|
+
Can be restricted to find only elements with specific tags,
|
2333
|
+
see `_Element.iter`.
|
2334
|
+
"""
|
2335
|
+
root = self.getroot()
|
2336
|
+
if root is None:
|
2337
|
+
return ITER_EMPTY
|
2338
|
+
if tag is not None:
|
2339
|
+
tags += (tag,)
|
2340
|
+
return root.iter(*tags)
|
2341
|
+
|
2342
|
+
def find(self, path, namespaces=None):
|
2343
|
+
"""find(self, path, namespaces=None)
|
2344
|
+
|
2345
|
+
Finds the first toplevel element with given tag. Same as
|
2346
|
+
``tree.getroot().find(path)``.
|
2347
|
+
|
2348
|
+
The optional ``namespaces`` argument accepts a
|
2349
|
+
prefix-to-namespace mapping that allows the usage of XPath
|
2350
|
+
prefixes in the path expression.
|
2351
|
+
"""
|
2352
|
+
self._assertHasRoot()
|
2353
|
+
root = self.getroot()
|
2354
|
+
if _isString(path):
|
2355
|
+
if path[:1] == "/":
|
2356
|
+
path = "." + path
|
2357
|
+
from warnings import warn
|
2358
|
+
warn(
|
2359
|
+
"This search incorrectly ignores the root element, and will be "
|
2360
|
+
"fixed in a future version. If you rely on the current "
|
2361
|
+
f"behaviour, change it to {path!r}",
|
2362
|
+
FutureWarning, stacklevel=1
|
2363
|
+
)
|
2364
|
+
return root.find(path, namespaces)
|
2365
|
+
|
2366
|
+
def findtext(self, path, default=None, namespaces=None):
|
2367
|
+
"""findtext(self, path, default=None, namespaces=None)
|
2368
|
+
|
2369
|
+
Finds the text for the first element matching the ElementPath
|
2370
|
+
expression. Same as getroot().findtext(path)
|
2371
|
+
|
2372
|
+
The optional ``namespaces`` argument accepts a
|
2373
|
+
prefix-to-namespace mapping that allows the usage of XPath
|
2374
|
+
prefixes in the path expression.
|
2375
|
+
"""
|
2376
|
+
self._assertHasRoot()
|
2377
|
+
root = self.getroot()
|
2378
|
+
if _isString(path):
|
2379
|
+
if path[:1] == "/":
|
2380
|
+
path = "." + path
|
2381
|
+
from warnings import warn
|
2382
|
+
warn(
|
2383
|
+
"This search incorrectly ignores the root element, and will be "
|
2384
|
+
"fixed in a future version. If you rely on the current "
|
2385
|
+
f"behaviour, change it to {path!r}",
|
2386
|
+
FutureWarning, stacklevel=1
|
2387
|
+
)
|
2388
|
+
return root.findtext(path, default, namespaces)
|
2389
|
+
|
2390
|
+
def findall(self, path, namespaces=None):
|
2391
|
+
"""findall(self, path, namespaces=None)
|
2392
|
+
|
2393
|
+
Finds all elements matching the ElementPath expression. Same as
|
2394
|
+
getroot().findall(path).
|
2395
|
+
|
2396
|
+
The optional ``namespaces`` argument accepts a
|
2397
|
+
prefix-to-namespace mapping that allows the usage of XPath
|
2398
|
+
prefixes in the path expression.
|
2399
|
+
"""
|
2400
|
+
self._assertHasRoot()
|
2401
|
+
root = self.getroot()
|
2402
|
+
if _isString(path):
|
2403
|
+
if path[:1] == "/":
|
2404
|
+
path = "." + path
|
2405
|
+
from warnings import warn
|
2406
|
+
warn(
|
2407
|
+
"This search incorrectly ignores the root element, and will be "
|
2408
|
+
"fixed in a future version. If you rely on the current "
|
2409
|
+
f"behaviour, change it to {path!r}",
|
2410
|
+
FutureWarning, stacklevel=1
|
2411
|
+
)
|
2412
|
+
return root.findall(path, namespaces)
|
2413
|
+
|
2414
|
+
def iterfind(self, path, namespaces=None):
|
2415
|
+
"""iterfind(self, path, namespaces=None)
|
2416
|
+
|
2417
|
+
Iterates over all elements matching the ElementPath expression.
|
2418
|
+
Same as getroot().iterfind(path).
|
2419
|
+
|
2420
|
+
The optional ``namespaces`` argument accepts a
|
2421
|
+
prefix-to-namespace mapping that allows the usage of XPath
|
2422
|
+
prefixes in the path expression.
|
2423
|
+
"""
|
2424
|
+
self._assertHasRoot()
|
2425
|
+
root = self.getroot()
|
2426
|
+
if _isString(path):
|
2427
|
+
if path[:1] == "/":
|
2428
|
+
path = "." + path
|
2429
|
+
from warnings import warn
|
2430
|
+
warn(
|
2431
|
+
"This search incorrectly ignores the root element, and will be "
|
2432
|
+
"fixed in a future version. If you rely on the current "
|
2433
|
+
f"behaviour, change it to {path!r}",
|
2434
|
+
FutureWarning, stacklevel=1
|
2435
|
+
)
|
2436
|
+
return root.iterfind(path, namespaces)
|
2437
|
+
|
2438
|
+
def xpath(self, _path, *, namespaces=None, extensions=None,
|
2439
|
+
smart_strings=True, **_variables):
|
2440
|
+
"""xpath(self, _path, namespaces=None, extensions=None, smart_strings=True, **_variables)
|
2441
|
+
|
2442
|
+
XPath evaluate in context of document.
|
2443
|
+
|
2444
|
+
``namespaces`` is an optional dictionary with prefix to namespace URI
|
2445
|
+
mappings, used by XPath. ``extensions`` defines additional extension
|
2446
|
+
functions.
|
2447
|
+
|
2448
|
+
Returns a list (nodeset), or bool, float or string.
|
2449
|
+
|
2450
|
+
In case of a list result, return Element for element nodes,
|
2451
|
+
string for text and attribute values.
|
2452
|
+
|
2453
|
+
Note: if you are going to apply multiple XPath expressions
|
2454
|
+
against the same document, it is more efficient to use
|
2455
|
+
XPathEvaluator directly.
|
2456
|
+
"""
|
2457
|
+
self._assertHasRoot()
|
2458
|
+
evaluator = XPathDocumentEvaluator(self, namespaces=namespaces,
|
2459
|
+
extensions=extensions,
|
2460
|
+
smart_strings=smart_strings)
|
2461
|
+
return evaluator(_path, **_variables)
|
2462
|
+
|
2463
|
+
def xslt(self, _xslt, extensions=None, access_control=None, **_kw):
|
2464
|
+
"""xslt(self, _xslt, extensions=None, access_control=None, **_kw)
|
2465
|
+
|
2466
|
+
Transform this document using other document.
|
2467
|
+
|
2468
|
+
xslt is a tree that should be XSLT
|
2469
|
+
keyword parameters are XSLT transformation parameters.
|
2470
|
+
|
2471
|
+
Returns the transformed tree.
|
2472
|
+
|
2473
|
+
Note: if you are going to apply the same XSLT stylesheet against
|
2474
|
+
multiple documents, it is more efficient to use the XSLT
|
2475
|
+
class directly.
|
2476
|
+
"""
|
2477
|
+
self._assertHasRoot()
|
2478
|
+
style = XSLT(_xslt, extensions=extensions,
|
2479
|
+
access_control=access_control)
|
2480
|
+
return style(self, **_kw)
|
2481
|
+
|
2482
|
+
def relaxng(self, relaxng):
|
2483
|
+
"""relaxng(self, relaxng)
|
2484
|
+
|
2485
|
+
Validate this document using other document.
|
2486
|
+
|
2487
|
+
The relaxng argument is a tree that should contain a Relax NG schema.
|
2488
|
+
|
2489
|
+
Returns True or False, depending on whether validation
|
2490
|
+
succeeded.
|
2491
|
+
|
2492
|
+
Note: if you are going to apply the same Relax NG schema against
|
2493
|
+
multiple documents, it is more efficient to use the RelaxNG
|
2494
|
+
class directly.
|
2495
|
+
"""
|
2496
|
+
self._assertHasRoot()
|
2497
|
+
schema = RelaxNG(relaxng)
|
2498
|
+
return schema.validate(self)
|
2499
|
+
|
2500
|
+
def xmlschema(self, xmlschema):
|
2501
|
+
"""xmlschema(self, xmlschema)
|
2502
|
+
|
2503
|
+
Validate this document using other document.
|
2504
|
+
|
2505
|
+
The xmlschema argument is a tree that should contain an XML Schema.
|
2506
|
+
|
2507
|
+
Returns True or False, depending on whether validation
|
2508
|
+
succeeded.
|
2509
|
+
|
2510
|
+
Note: If you are going to apply the same XML Schema against
|
2511
|
+
multiple documents, it is more efficient to use the XMLSchema
|
2512
|
+
class directly.
|
2513
|
+
"""
|
2514
|
+
self._assertHasRoot()
|
2515
|
+
schema = XMLSchema(xmlschema)
|
2516
|
+
return schema.validate(self)
|
2517
|
+
|
2518
|
+
def xinclude(self):
|
2519
|
+
"""xinclude(self)
|
2520
|
+
|
2521
|
+
Process the XInclude nodes in this document and include the
|
2522
|
+
referenced XML fragments.
|
2523
|
+
|
2524
|
+
There is support for loading files through the file system, HTTP and
|
2525
|
+
FTP.
|
2526
|
+
|
2527
|
+
Note that XInclude does not support custom resolvers in Python space
|
2528
|
+
due to restrictions of libxml2 <= 2.6.29.
|
2529
|
+
"""
|
2530
|
+
self._assertHasRoot()
|
2531
|
+
XInclude()(self._context_node)
|
2532
|
+
|
2533
|
+
def write_c14n(self, file, *, bint exclusive=False, bint with_comments=True,
|
2534
|
+
compression=0, inclusive_ns_prefixes=None):
|
2535
|
+
"""write_c14n(self, file, exclusive=False, with_comments=True,
|
2536
|
+
compression=0, inclusive_ns_prefixes=None)
|
2537
|
+
|
2538
|
+
C14N write of document. Always writes UTF-8.
|
2539
|
+
|
2540
|
+
The ``compression`` option enables GZip compression level 1-9.
|
2541
|
+
|
2542
|
+
The ``inclusive_ns_prefixes`` should be a list of namespace strings
|
2543
|
+
(i.e. ['xs', 'xsi']) that will be promoted to the top-level element
|
2544
|
+
during exclusive C14N serialisation. This parameter is ignored if
|
2545
|
+
exclusive mode=False.
|
2546
|
+
|
2547
|
+
If exclusive=True and no list is provided, a namespace will only be
|
2548
|
+
rendered if it is used by the immediate parent or one of its attributes
|
2549
|
+
and its prefix and values have not already been rendered by an ancestor
|
2550
|
+
of the namespace node's parent element.
|
2551
|
+
|
2552
|
+
NOTE: This method is deprecated as of lxml 4.4 and will be removed in a
|
2553
|
+
future release. Use ``.write(f, method="c14n")`` instead.
|
2554
|
+
"""
|
2555
|
+
self._assertHasRoot()
|
2556
|
+
_assertValidNode(self._context_node)
|
2557
|
+
if compression is None or compression < 0:
|
2558
|
+
compression = 0
|
2559
|
+
|
2560
|
+
_tofilelikeC14N(file, self._context_node, exclusive, with_comments,
|
2561
|
+
compression, inclusive_ns_prefixes)
|
2562
|
+
|
2563
|
+
cdef _ElementTree _elementTreeFactory(_Document doc, _Element context_node):
|
2564
|
+
return _newElementTree(doc, context_node, _ElementTree)
|
2565
|
+
|
2566
|
+
cdef _ElementTree _newElementTree(_Document doc, _Element context_node,
|
2567
|
+
object baseclass):
|
2568
|
+
cdef _ElementTree result
|
2569
|
+
result = baseclass()
|
2570
|
+
if context_node is None and doc is not None:
|
2571
|
+
context_node = doc.getroot()
|
2572
|
+
if context_node is None:
|
2573
|
+
_assertValidDoc(doc)
|
2574
|
+
result._doc = doc
|
2575
|
+
else:
|
2576
|
+
_assertValidNode(context_node)
|
2577
|
+
result._context_node = context_node
|
2578
|
+
return result
|
2579
|
+
|
2580
|
+
|
2581
|
+
@cython.final
|
2582
|
+
@cython.freelist(16)
|
2583
|
+
cdef class _Attrib:
|
2584
|
+
"""A dict-like proxy for the ``Element.attrib`` property.
|
2585
|
+
"""
|
2586
|
+
cdef _Element _element
|
2587
|
+
def __cinit__(self, _Element element not None):
|
2588
|
+
_assertValidNode(element)
|
2589
|
+
self._element = element
|
2590
|
+
|
2591
|
+
# MANIPULATORS
|
2592
|
+
def __setitem__(self, key, value):
|
2593
|
+
_assertValidNode(self._element)
|
2594
|
+
_setAttributeValue(self._element, key, value)
|
2595
|
+
|
2596
|
+
def __delitem__(self, key):
|
2597
|
+
_assertValidNode(self._element)
|
2598
|
+
_delAttribute(self._element, key)
|
2599
|
+
|
2600
|
+
def update(self, sequence_or_dict):
|
2601
|
+
_assertValidNode(self._element)
|
2602
|
+
if isinstance(sequence_or_dict, (dict, _Attrib)):
|
2603
|
+
sequence_or_dict = sequence_or_dict.items()
|
2604
|
+
for key, value in sequence_or_dict:
|
2605
|
+
_setAttributeValue(self._element, key, value)
|
2606
|
+
|
2607
|
+
def pop(self, key, *default):
|
2608
|
+
if len(default) > 1:
|
2609
|
+
raise TypeError, f"pop expected at most 2 arguments, got {len(default)+1}"
|
2610
|
+
_assertValidNode(self._element)
|
2611
|
+
result = _getAttributeValue(self._element, key, None)
|
2612
|
+
if result is None:
|
2613
|
+
if not default:
|
2614
|
+
raise KeyError, key
|
2615
|
+
result = default[0]
|
2616
|
+
else:
|
2617
|
+
_delAttribute(self._element, key)
|
2618
|
+
return result
|
2619
|
+
|
2620
|
+
def clear(self):
|
2621
|
+
_assertValidNode(self._element)
|
2622
|
+
c_attrs = self._element._c_node.properties
|
2623
|
+
if c_attrs:
|
2624
|
+
self._element._c_node.properties = NULL
|
2625
|
+
tree.xmlFreePropList(c_attrs)
|
2626
|
+
|
2627
|
+
# ACCESSORS
|
2628
|
+
def __repr__(self):
|
2629
|
+
_assertValidNode(self._element)
|
2630
|
+
return repr(dict( _collectAttributes(self._element._c_node, 3) ))
|
2631
|
+
|
2632
|
+
def __copy__(self):
|
2633
|
+
_assertValidNode(self._element)
|
2634
|
+
return dict(_collectAttributes(self._element._c_node, 3))
|
2635
|
+
|
2636
|
+
def __deepcopy__(self, memo):
|
2637
|
+
_assertValidNode(self._element)
|
2638
|
+
return dict(_collectAttributes(self._element._c_node, 3))
|
2639
|
+
|
2640
|
+
def __getitem__(self, key):
|
2641
|
+
_assertValidNode(self._element)
|
2642
|
+
result = _getAttributeValue(self._element, key, None)
|
2643
|
+
if result is None:
|
2644
|
+
raise KeyError, key
|
2645
|
+
return result
|
2646
|
+
|
2647
|
+
def __bool__(self):
|
2648
|
+
_assertValidNode(self._element)
|
2649
|
+
cdef xmlAttr* c_attr = self._element._c_node.properties
|
2650
|
+
while c_attr is not NULL:
|
2651
|
+
if c_attr.type == tree.XML_ATTRIBUTE_NODE:
|
2652
|
+
return 1
|
2653
|
+
c_attr = c_attr.next
|
2654
|
+
return 0
|
2655
|
+
|
2656
|
+
def __len__(self):
|
2657
|
+
_assertValidNode(self._element)
|
2658
|
+
cdef xmlAttr* c_attr = self._element._c_node.properties
|
2659
|
+
cdef Py_ssize_t c = 0
|
2660
|
+
while c_attr is not NULL:
|
2661
|
+
if c_attr.type == tree.XML_ATTRIBUTE_NODE:
|
2662
|
+
c += 1
|
2663
|
+
c_attr = c_attr.next
|
2664
|
+
return c
|
2665
|
+
|
2666
|
+
def get(self, key, default=None):
|
2667
|
+
_assertValidNode(self._element)
|
2668
|
+
return _getAttributeValue(self._element, key, default)
|
2669
|
+
|
2670
|
+
def keys(self):
|
2671
|
+
_assertValidNode(self._element)
|
2672
|
+
return _collectAttributes(self._element._c_node, 1)
|
2673
|
+
|
2674
|
+
def __iter__(self):
|
2675
|
+
_assertValidNode(self._element)
|
2676
|
+
return iter(_collectAttributes(self._element._c_node, 1))
|
2677
|
+
|
2678
|
+
def iterkeys(self):
|
2679
|
+
_assertValidNode(self._element)
|
2680
|
+
return iter(_collectAttributes(self._element._c_node, 1))
|
2681
|
+
|
2682
|
+
def values(self):
|
2683
|
+
_assertValidNode(self._element)
|
2684
|
+
return _collectAttributes(self._element._c_node, 2)
|
2685
|
+
|
2686
|
+
def itervalues(self):
|
2687
|
+
_assertValidNode(self._element)
|
2688
|
+
return iter(_collectAttributes(self._element._c_node, 2))
|
2689
|
+
|
2690
|
+
def items(self):
|
2691
|
+
_assertValidNode(self._element)
|
2692
|
+
return _collectAttributes(self._element._c_node, 3)
|
2693
|
+
|
2694
|
+
def iteritems(self):
|
2695
|
+
_assertValidNode(self._element)
|
2696
|
+
return iter(_collectAttributes(self._element._c_node, 3))
|
2697
|
+
|
2698
|
+
def has_key(self, key):
|
2699
|
+
_assertValidNode(self._element)
|
2700
|
+
return key in self
|
2701
|
+
|
2702
|
+
def __contains__(self, key):
|
2703
|
+
_assertValidNode(self._element)
|
2704
|
+
cdef xmlNode* c_node
|
2705
|
+
ns, tag = _getNsTag(key)
|
2706
|
+
c_node = self._element._c_node
|
2707
|
+
c_href = <const_xmlChar*>NULL if ns is None else _xcstr(ns)
|
2708
|
+
return 1 if tree.xmlHasNsProp(c_node, _xcstr(tag), c_href) else 0
|
2709
|
+
|
2710
|
+
def __richcmp__(self, other, int op):
|
2711
|
+
try:
|
2712
|
+
one = dict(self.items())
|
2713
|
+
if not isinstance(other, dict):
|
2714
|
+
other = dict(other)
|
2715
|
+
except (TypeError, ValueError):
|
2716
|
+
return NotImplemented
|
2717
|
+
return python.PyObject_RichCompare(one, other, op)
|
2718
|
+
|
2719
|
+
MutableMapping.register(_Attrib)
|
2720
|
+
|
2721
|
+
|
2722
|
+
@cython.final
|
2723
|
+
@cython.internal
|
2724
|
+
cdef class _AttribIterator:
|
2725
|
+
"""Attribute iterator - for internal use only!
|
2726
|
+
"""
|
2727
|
+
# XML attributes must not be removed while running!
|
2728
|
+
cdef _Element _node
|
2729
|
+
cdef xmlAttr* _c_attr
|
2730
|
+
cdef int _keysvalues # 1 - keys, 2 - values, 3 - items (key, value)
|
2731
|
+
def __iter__(self):
|
2732
|
+
return self
|
2733
|
+
|
2734
|
+
def __next__(self):
|
2735
|
+
cdef xmlAttr* c_attr
|
2736
|
+
if self._node is None:
|
2737
|
+
raise StopIteration
|
2738
|
+
c_attr = self._c_attr
|
2739
|
+
while c_attr is not NULL and c_attr.type != tree.XML_ATTRIBUTE_NODE:
|
2740
|
+
c_attr = c_attr.next
|
2741
|
+
if c_attr is NULL:
|
2742
|
+
self._node = None
|
2743
|
+
raise StopIteration
|
2744
|
+
|
2745
|
+
self._c_attr = c_attr.next
|
2746
|
+
if self._keysvalues == 1:
|
2747
|
+
return _namespacedName(<xmlNode*>c_attr)
|
2748
|
+
elif self._keysvalues == 2:
|
2749
|
+
return _attributeValue(self._node._c_node, c_attr)
|
2750
|
+
else:
|
2751
|
+
return (_namespacedName(<xmlNode*>c_attr),
|
2752
|
+
_attributeValue(self._node._c_node, c_attr))
|
2753
|
+
|
2754
|
+
cdef object _attributeIteratorFactory(_Element element, int keysvalues):
|
2755
|
+
cdef _AttribIterator attribs
|
2756
|
+
if element._c_node.properties is NULL:
|
2757
|
+
return ITER_EMPTY
|
2758
|
+
attribs = _AttribIterator()
|
2759
|
+
attribs._node = element
|
2760
|
+
attribs._c_attr = element._c_node.properties
|
2761
|
+
attribs._keysvalues = keysvalues
|
2762
|
+
return attribs
|
2763
|
+
|
2764
|
+
|
2765
|
+
cdef public class _ElementTagMatcher [ object LxmlElementTagMatcher,
|
2766
|
+
type LxmlElementTagMatcherType ]:
|
2767
|
+
"""
|
2768
|
+
Dead but public. :)
|
2769
|
+
"""
|
2770
|
+
cdef object _pystrings
|
2771
|
+
cdef int _node_type
|
2772
|
+
cdef char* _href
|
2773
|
+
cdef char* _name
|
2774
|
+
cdef _initTagMatch(self, tag):
|
2775
|
+
self._href = NULL
|
2776
|
+
self._name = NULL
|
2777
|
+
if tag is None:
|
2778
|
+
self._node_type = 0
|
2779
|
+
elif tag is Comment:
|
2780
|
+
self._node_type = tree.XML_COMMENT_NODE
|
2781
|
+
elif tag is ProcessingInstruction:
|
2782
|
+
self._node_type = tree.XML_PI_NODE
|
2783
|
+
elif tag is Entity:
|
2784
|
+
self._node_type = tree.XML_ENTITY_REF_NODE
|
2785
|
+
elif tag is Element:
|
2786
|
+
self._node_type = tree.XML_ELEMENT_NODE
|
2787
|
+
else:
|
2788
|
+
self._node_type = tree.XML_ELEMENT_NODE
|
2789
|
+
self._pystrings = _getNsTag(tag)
|
2790
|
+
if self._pystrings[0] is not None:
|
2791
|
+
self._href = _cstr(self._pystrings[0])
|
2792
|
+
self._name = _cstr(self._pystrings[1])
|
2793
|
+
if self._name[0] == c'*' and self._name[1] == c'\0':
|
2794
|
+
self._name = NULL
|
2795
|
+
|
2796
|
+
cdef public class _ElementIterator(_ElementTagMatcher) [
|
2797
|
+
object LxmlElementIterator, type LxmlElementIteratorType ]:
|
2798
|
+
"""
|
2799
|
+
Dead but public. :)
|
2800
|
+
"""
|
2801
|
+
# we keep Python references here to control GC
|
2802
|
+
cdef _Element _node
|
2803
|
+
cdef _node_to_node_function _next_element
|
2804
|
+
def __iter__(self):
|
2805
|
+
return self
|
2806
|
+
|
2807
|
+
cdef void _storeNext(self, _Element node):
|
2808
|
+
cdef xmlNode* c_node
|
2809
|
+
c_node = self._next_element(node._c_node)
|
2810
|
+
while c_node is not NULL and \
|
2811
|
+
self._node_type != 0 and \
|
2812
|
+
(<tree.xmlElementType>self._node_type != c_node.type or
|
2813
|
+
not _tagMatches(c_node, <const_xmlChar*>self._href, <const_xmlChar*>self._name)):
|
2814
|
+
c_node = self._next_element(c_node)
|
2815
|
+
if c_node is NULL:
|
2816
|
+
self._node = None
|
2817
|
+
else:
|
2818
|
+
# Python ref:
|
2819
|
+
self._node = _elementFactory(node._doc, c_node)
|
2820
|
+
|
2821
|
+
def __next__(self):
|
2822
|
+
cdef xmlNode* c_node
|
2823
|
+
cdef _Element current_node
|
2824
|
+
if self._node is None:
|
2825
|
+
raise StopIteration
|
2826
|
+
# Python ref:
|
2827
|
+
current_node = self._node
|
2828
|
+
self._storeNext(current_node)
|
2829
|
+
return current_node
|
2830
|
+
|
2831
|
+
@cython.final
|
2832
|
+
@cython.internal
|
2833
|
+
cdef class _MultiTagMatcher:
|
2834
|
+
"""
|
2835
|
+
Match an xmlNode against a list of tags.
|
2836
|
+
"""
|
2837
|
+
cdef list _py_tags
|
2838
|
+
cdef qname* _cached_tags
|
2839
|
+
cdef size_t _tag_count
|
2840
|
+
cdef size_t _cached_size
|
2841
|
+
cdef _Document _cached_doc
|
2842
|
+
cdef int _node_types
|
2843
|
+
|
2844
|
+
def __cinit__(self, tags):
|
2845
|
+
self._py_tags = []
|
2846
|
+
self.initTagMatch(tags)
|
2847
|
+
|
2848
|
+
def __dealloc__(self):
|
2849
|
+
self._clear()
|
2850
|
+
|
2851
|
+
cdef bint rejectsAll(self) noexcept:
|
2852
|
+
return not self._tag_count and not self._node_types
|
2853
|
+
|
2854
|
+
cdef bint rejectsAllAttributes(self) noexcept:
|
2855
|
+
return not self._tag_count
|
2856
|
+
|
2857
|
+
cdef bint matchesType(self, int node_type) noexcept:
|
2858
|
+
if node_type == tree.XML_ELEMENT_NODE and self._tag_count:
|
2859
|
+
return True
|
2860
|
+
return self._node_types & (1 << node_type)
|
2861
|
+
|
2862
|
+
cdef void _clear(self) noexcept:
|
2863
|
+
cdef size_t i, count
|
2864
|
+
count = self._tag_count
|
2865
|
+
self._tag_count = 0
|
2866
|
+
if self._cached_tags:
|
2867
|
+
for i in range(count):
|
2868
|
+
cpython.ref.Py_XDECREF(self._cached_tags[i].href)
|
2869
|
+
python.lxml_free(self._cached_tags)
|
2870
|
+
self._cached_tags = NULL
|
2871
|
+
|
2872
|
+
cdef initTagMatch(self, tags):
|
2873
|
+
self._cached_doc = None
|
2874
|
+
del self._py_tags[:]
|
2875
|
+
self._clear()
|
2876
|
+
if tags is None or tags == ():
|
2877
|
+
# no selection in tags argument => match anything
|
2878
|
+
self._node_types = (
|
2879
|
+
1 << tree.XML_COMMENT_NODE |
|
2880
|
+
1 << tree.XML_PI_NODE |
|
2881
|
+
1 << tree.XML_ENTITY_REF_NODE |
|
2882
|
+
1 << tree.XML_ELEMENT_NODE)
|
2883
|
+
else:
|
2884
|
+
self._node_types = 0
|
2885
|
+
self._storeTags(tags, set())
|
2886
|
+
|
2887
|
+
cdef _storeTags(self, tag, set seen):
|
2888
|
+
if tag is Comment:
|
2889
|
+
self._node_types |= 1 << tree.XML_COMMENT_NODE
|
2890
|
+
elif tag is ProcessingInstruction:
|
2891
|
+
self._node_types |= 1 << tree.XML_PI_NODE
|
2892
|
+
elif tag is Entity:
|
2893
|
+
self._node_types |= 1 << tree.XML_ENTITY_REF_NODE
|
2894
|
+
elif tag is Element:
|
2895
|
+
self._node_types |= 1 << tree.XML_ELEMENT_NODE
|
2896
|
+
elif python._isString(tag):
|
2897
|
+
if tag in seen:
|
2898
|
+
return
|
2899
|
+
seen.add(tag)
|
2900
|
+
if tag in ('*', '{*}*'):
|
2901
|
+
self._node_types |= 1 << tree.XML_ELEMENT_NODE
|
2902
|
+
else:
|
2903
|
+
href, name = _getNsTag(tag)
|
2904
|
+
if name == b'*':
|
2905
|
+
name = None
|
2906
|
+
if href is None:
|
2907
|
+
href = b'' # no namespace
|
2908
|
+
elif href == b'*':
|
2909
|
+
href = None # wildcard: any namespace, including none
|
2910
|
+
self._py_tags.append((href, name))
|
2911
|
+
elif isinstance(tag, QName):
|
2912
|
+
self._storeTags(tag.text, seen)
|
2913
|
+
else:
|
2914
|
+
# support a sequence of tags
|
2915
|
+
for item in tag:
|
2916
|
+
self._storeTags(item, seen)
|
2917
|
+
|
2918
|
+
cdef inline int cacheTags(self, _Document doc, bint force_into_dict=False) except -1:
|
2919
|
+
"""
|
2920
|
+
Look up the tag names in the doc dict to enable string pointer comparisons.
|
2921
|
+
"""
|
2922
|
+
cdef size_t dict_size = tree.xmlDictSize(doc._c_doc.dict)
|
2923
|
+
if doc is self._cached_doc and dict_size == self._cached_size:
|
2924
|
+
# doc and dict didn't change => names already cached
|
2925
|
+
return 0
|
2926
|
+
self._tag_count = 0
|
2927
|
+
if not self._py_tags:
|
2928
|
+
self._cached_doc = doc
|
2929
|
+
self._cached_size = dict_size
|
2930
|
+
return 0
|
2931
|
+
if not self._cached_tags:
|
2932
|
+
self._cached_tags = <qname*>python.lxml_malloc(len(self._py_tags), sizeof(qname))
|
2933
|
+
if not self._cached_tags:
|
2934
|
+
self._cached_doc = None
|
2935
|
+
raise MemoryError()
|
2936
|
+
self._tag_count = <size_t>_mapTagsToQnameMatchArray(
|
2937
|
+
doc._c_doc, self._py_tags, self._cached_tags, force_into_dict)
|
2938
|
+
self._cached_doc = doc
|
2939
|
+
self._cached_size = dict_size
|
2940
|
+
return 0
|
2941
|
+
|
2942
|
+
cdef inline bint matches(self, xmlNode* c_node) noexcept:
|
2943
|
+
cdef qname* c_qname
|
2944
|
+
if self._node_types & (1 << c_node.type):
|
2945
|
+
return True
|
2946
|
+
elif c_node.type == tree.XML_ELEMENT_NODE:
|
2947
|
+
for c_qname in self._cached_tags[:self._tag_count]:
|
2948
|
+
if _tagMatchesExactly(c_node, c_qname):
|
2949
|
+
return True
|
2950
|
+
return False
|
2951
|
+
|
2952
|
+
cdef inline bint matchesNsTag(self, const_xmlChar* c_href,
|
2953
|
+
const_xmlChar* c_name) noexcept:
|
2954
|
+
cdef qname* c_qname
|
2955
|
+
if self._node_types & (1 << tree.XML_ELEMENT_NODE):
|
2956
|
+
return True
|
2957
|
+
for c_qname in self._cached_tags[:self._tag_count]:
|
2958
|
+
if _nsTagMatchesExactly(c_href, c_name, c_qname):
|
2959
|
+
return True
|
2960
|
+
return False
|
2961
|
+
|
2962
|
+
cdef inline bint matchesAttribute(self, xmlAttr* c_attr) noexcept:
|
2963
|
+
"""Attribute matches differ from Element matches in that they do
|
2964
|
+
not care about node types.
|
2965
|
+
"""
|
2966
|
+
cdef qname* c_qname
|
2967
|
+
for c_qname in self._cached_tags[:self._tag_count]:
|
2968
|
+
if _tagMatchesExactly(<xmlNode*>c_attr, c_qname):
|
2969
|
+
return True
|
2970
|
+
return False
|
2971
|
+
|
2972
|
+
cdef class _ElementMatchIterator:
|
2973
|
+
cdef _Element _node
|
2974
|
+
cdef _node_to_node_function _next_element
|
2975
|
+
cdef _MultiTagMatcher _matcher
|
2976
|
+
|
2977
|
+
@cython.final
|
2978
|
+
cdef _initTagMatcher(self, tags):
|
2979
|
+
self._matcher = _MultiTagMatcher.__new__(_MultiTagMatcher, tags)
|
2980
|
+
|
2981
|
+
def __iter__(self):
|
2982
|
+
return self
|
2983
|
+
|
2984
|
+
@cython.final
|
2985
|
+
cdef int _storeNext(self, _Element node) except -1:
|
2986
|
+
self._matcher.cacheTags(node._doc)
|
2987
|
+
c_node = self._next_element(node._c_node)
|
2988
|
+
while c_node is not NULL and not self._matcher.matches(c_node):
|
2989
|
+
c_node = self._next_element(c_node)
|
2990
|
+
# store Python ref to next node to make sure it's kept alive
|
2991
|
+
self._node = _elementFactory(node._doc, c_node) if c_node is not NULL else None
|
2992
|
+
return 0
|
2993
|
+
|
2994
|
+
def __next__(self):
|
2995
|
+
cdef _Element current_node = self._node
|
2996
|
+
if current_node is None:
|
2997
|
+
raise StopIteration
|
2998
|
+
self._storeNext(current_node)
|
2999
|
+
return current_node
|
3000
|
+
|
3001
|
+
cdef class ElementChildIterator(_ElementMatchIterator):
|
3002
|
+
"""ElementChildIterator(self, node, tag=None, reversed=False)
|
3003
|
+
Iterates over the children of an element.
|
3004
|
+
"""
|
3005
|
+
def __cinit__(self, _Element node not None, tag=None, *, bint reversed=False):
|
3006
|
+
cdef xmlNode* c_node
|
3007
|
+
_assertValidNode(node)
|
3008
|
+
self._initTagMatcher(tag)
|
3009
|
+
if reversed:
|
3010
|
+
c_node = _findChildBackwards(node._c_node, 0)
|
3011
|
+
self._next_element = _previousElement
|
3012
|
+
else:
|
3013
|
+
c_node = _findChildForwards(node._c_node, 0)
|
3014
|
+
self._next_element = _nextElement
|
3015
|
+
self._matcher.cacheTags(node._doc)
|
3016
|
+
while c_node is not NULL and not self._matcher.matches(c_node):
|
3017
|
+
c_node = self._next_element(c_node)
|
3018
|
+
# store Python ref to next node to make sure it's kept alive
|
3019
|
+
self._node = _elementFactory(node._doc, c_node) if c_node is not NULL else None
|
3020
|
+
|
3021
|
+
cdef class SiblingsIterator(_ElementMatchIterator):
|
3022
|
+
"""SiblingsIterator(self, node, tag=None, preceding=False)
|
3023
|
+
Iterates over the siblings of an element.
|
3024
|
+
|
3025
|
+
You can pass the boolean keyword ``preceding`` to specify the direction.
|
3026
|
+
"""
|
3027
|
+
def __cinit__(self, _Element node not None, tag=None, *, bint preceding=False):
|
3028
|
+
_assertValidNode(node)
|
3029
|
+
self._initTagMatcher(tag)
|
3030
|
+
if preceding:
|
3031
|
+
self._next_element = _previousElement
|
3032
|
+
else:
|
3033
|
+
self._next_element = _nextElement
|
3034
|
+
self._storeNext(node)
|
3035
|
+
|
3036
|
+
cdef class AncestorsIterator(_ElementMatchIterator):
|
3037
|
+
"""AncestorsIterator(self, node, tag=None)
|
3038
|
+
Iterates over the ancestors of an element (from parent to parent).
|
3039
|
+
"""
|
3040
|
+
def __cinit__(self, _Element node not None, tag=None):
|
3041
|
+
_assertValidNode(node)
|
3042
|
+
self._initTagMatcher(tag)
|
3043
|
+
self._next_element = _parentElement
|
3044
|
+
self._storeNext(node)
|
3045
|
+
|
3046
|
+
cdef class ElementDepthFirstIterator:
|
3047
|
+
"""ElementDepthFirstIterator(self, node, tag=None, inclusive=True)
|
3048
|
+
Iterates over an element and its sub-elements in document order (depth
|
3049
|
+
first pre-order).
|
3050
|
+
|
3051
|
+
Note that this also includes comments, entities and processing
|
3052
|
+
instructions. To filter them out, check if the ``tag`` property
|
3053
|
+
of the returned element is a string (i.e. not None and not a
|
3054
|
+
factory function), or pass the ``Element`` factory for the ``tag``
|
3055
|
+
argument to receive only Elements.
|
3056
|
+
|
3057
|
+
If the optional ``tag`` argument is not None, the iterator returns only
|
3058
|
+
the elements that match the respective name and namespace.
|
3059
|
+
|
3060
|
+
The optional boolean argument 'inclusive' defaults to True and can be set
|
3061
|
+
to False to exclude the start element itself.
|
3062
|
+
|
3063
|
+
Note that the behaviour of this iterator is completely undefined if the
|
3064
|
+
tree it traverses is modified during iteration.
|
3065
|
+
"""
|
3066
|
+
# we keep Python references here to control GC
|
3067
|
+
# keep the next Element after the one we return, and the (s)top node
|
3068
|
+
cdef _Element _next_node
|
3069
|
+
cdef _Element _top_node
|
3070
|
+
cdef _MultiTagMatcher _matcher
|
3071
|
+
def __cinit__(self, _Element node not None, tag=None, *, bint inclusive=True):
|
3072
|
+
_assertValidNode(node)
|
3073
|
+
self._top_node = node
|
3074
|
+
self._next_node = node
|
3075
|
+
self._matcher = _MultiTagMatcher.__new__(_MultiTagMatcher, tag)
|
3076
|
+
self._matcher.cacheTags(node._doc)
|
3077
|
+
if not inclusive or not self._matcher.matches(node._c_node):
|
3078
|
+
# find start node (this cannot raise StopIteration, self._next_node != None)
|
3079
|
+
next(self)
|
3080
|
+
|
3081
|
+
def __iter__(self):
|
3082
|
+
return self
|
3083
|
+
|
3084
|
+
def __next__(self):
|
3085
|
+
cdef xmlNode* c_node
|
3086
|
+
cdef _Element current_node = self._next_node
|
3087
|
+
if current_node is None:
|
3088
|
+
raise StopIteration
|
3089
|
+
c_node = current_node._c_node
|
3090
|
+
self._matcher.cacheTags(current_node._doc)
|
3091
|
+
if not self._matcher._tag_count:
|
3092
|
+
# no tag name was found in the dict => not in document either
|
3093
|
+
# try to match by node type
|
3094
|
+
c_node = self._nextNodeAnyTag(c_node)
|
3095
|
+
else:
|
3096
|
+
c_node = self._nextNodeMatchTag(c_node)
|
3097
|
+
if c_node is NULL:
|
3098
|
+
self._next_node = None
|
3099
|
+
else:
|
3100
|
+
self._next_node = _elementFactory(current_node._doc, c_node)
|
3101
|
+
return current_node
|
3102
|
+
|
3103
|
+
@cython.final
|
3104
|
+
cdef xmlNode* _nextNodeAnyTag(self, xmlNode* c_node) noexcept:
|
3105
|
+
cdef int node_types = self._matcher._node_types
|
3106
|
+
if not node_types:
|
3107
|
+
return NULL
|
3108
|
+
tree.BEGIN_FOR_EACH_ELEMENT_FROM(self._top_node._c_node, c_node, 0)
|
3109
|
+
if node_types & (1 << c_node.type):
|
3110
|
+
return c_node
|
3111
|
+
tree.END_FOR_EACH_ELEMENT_FROM(c_node)
|
3112
|
+
return NULL
|
3113
|
+
|
3114
|
+
@cython.final
|
3115
|
+
cdef xmlNode* _nextNodeMatchTag(self, xmlNode* c_node) noexcept:
|
3116
|
+
tree.BEGIN_FOR_EACH_ELEMENT_FROM(self._top_node._c_node, c_node, 0)
|
3117
|
+
if self._matcher.matches(c_node):
|
3118
|
+
return c_node
|
3119
|
+
tree.END_FOR_EACH_ELEMENT_FROM(c_node)
|
3120
|
+
return NULL
|
3121
|
+
|
3122
|
+
|
3123
|
+
cdef class ElementTextIterator:
|
3124
|
+
"""ElementTextIterator(self, element, tag=None, with_tail=True)
|
3125
|
+
Iterates over the text content of a subtree.
|
3126
|
+
|
3127
|
+
You can pass the ``tag`` keyword argument to restrict text content to a
|
3128
|
+
specific tag name.
|
3129
|
+
|
3130
|
+
You can set the ``with_tail`` keyword argument to ``False`` to skip over
|
3131
|
+
tail text (e.g. if you know that it's only whitespace from pretty-printing).
|
3132
|
+
"""
|
3133
|
+
cdef object _events
|
3134
|
+
cdef _Element _start_element
|
3135
|
+
def __cinit__(self, _Element element not None, tag=None, *, bint with_tail=True):
|
3136
|
+
_assertValidNode(element)
|
3137
|
+
if with_tail:
|
3138
|
+
events = ("start", "comment", "pi", "end")
|
3139
|
+
else:
|
3140
|
+
events = ("start",)
|
3141
|
+
self._start_element = element
|
3142
|
+
self._events = iterwalk(element, events=events, tag=tag)
|
3143
|
+
|
3144
|
+
def __iter__(self):
|
3145
|
+
return self
|
3146
|
+
|
3147
|
+
def __next__(self):
|
3148
|
+
cdef _Element element
|
3149
|
+
result = None
|
3150
|
+
while result is None:
|
3151
|
+
event, element = next(self._events) # raises StopIteration
|
3152
|
+
if event == "start":
|
3153
|
+
result = element.text
|
3154
|
+
elif element is not self._start_element:
|
3155
|
+
result = element.tail
|
3156
|
+
return result
|
3157
|
+
|
3158
|
+
|
3159
|
+
cdef xmlNode* _createElement(xmlDoc* c_doc, object name_utf) except NULL:
|
3160
|
+
cdef xmlNode* c_node
|
3161
|
+
c_node = tree.xmlNewDocNode(c_doc, NULL, _xcstr(name_utf), NULL)
|
3162
|
+
return c_node
|
3163
|
+
|
3164
|
+
cdef xmlNode* _createComment(xmlDoc* c_doc, const_xmlChar* text) noexcept:
|
3165
|
+
cdef xmlNode* c_node
|
3166
|
+
c_node = tree.xmlNewDocComment(c_doc, text)
|
3167
|
+
return c_node
|
3168
|
+
|
3169
|
+
cdef xmlNode* _createPI(xmlDoc* c_doc, const_xmlChar* target, const_xmlChar* text) noexcept:
|
3170
|
+
cdef xmlNode* c_node
|
3171
|
+
c_node = tree.xmlNewDocPI(c_doc, target, text)
|
3172
|
+
return c_node
|
3173
|
+
|
3174
|
+
cdef xmlNode* _createEntity(xmlDoc* c_doc, const_xmlChar* name) noexcept:
|
3175
|
+
cdef xmlNode* c_node
|
3176
|
+
c_node = tree.xmlNewReference(c_doc, name)
|
3177
|
+
return c_node
|
3178
|
+
|
3179
|
+
# module-level API for ElementTree
|
3180
|
+
|
3181
|
+
from abc import ABC
|
3182
|
+
|
3183
|
+
class Element(ABC):
|
3184
|
+
"""Element(_tag, attrib=None, nsmap=None, **_extra)
|
3185
|
+
|
3186
|
+
Element factory, as a class.
|
3187
|
+
|
3188
|
+
An instance of this class is an object implementing the
|
3189
|
+
Element interface.
|
3190
|
+
|
3191
|
+
>>> element = Element("test")
|
3192
|
+
>>> type(element)
|
3193
|
+
<class 'lxml.etree._Element'>
|
3194
|
+
>>> isinstance(element, Element)
|
3195
|
+
True
|
3196
|
+
>>> issubclass(_Element, Element)
|
3197
|
+
True
|
3198
|
+
|
3199
|
+
Also look at the `_Element.makeelement()` and
|
3200
|
+
`_BaseParser.makeelement()` methods, which provide a faster way to
|
3201
|
+
create an Element within a specific document or parser context.
|
3202
|
+
"""
|
3203
|
+
def __new__(cls, _tag, attrib=None, nsmap=None, **_extra):
|
3204
|
+
return _makeElement(_tag, NULL, None, None, None, None,
|
3205
|
+
attrib, nsmap, _extra)
|
3206
|
+
|
3207
|
+
# Register _Element as a virtual subclass of Element
|
3208
|
+
Element.register(_Element)
|
3209
|
+
|
3210
|
+
|
3211
|
+
def Comment(text=None):
|
3212
|
+
"""Comment(text=None)
|
3213
|
+
|
3214
|
+
Comment element factory. This factory function creates a special element that will
|
3215
|
+
be serialized as an XML comment.
|
3216
|
+
"""
|
3217
|
+
cdef _Document doc
|
3218
|
+
cdef xmlNode* c_node
|
3219
|
+
cdef xmlDoc* c_doc
|
3220
|
+
|
3221
|
+
if text is None:
|
3222
|
+
text = b''
|
3223
|
+
else:
|
3224
|
+
text = _utf8(text)
|
3225
|
+
if b'--' in text or text.endswith(b'-'):
|
3226
|
+
raise ValueError("Comment may not contain '--' or end with '-'")
|
3227
|
+
|
3228
|
+
c_doc = _newXMLDoc()
|
3229
|
+
doc = _documentFactory(c_doc, None)
|
3230
|
+
c_node = _createComment(c_doc, _xcstr(text))
|
3231
|
+
tree.xmlAddChild(<xmlNode*>c_doc, c_node)
|
3232
|
+
return _elementFactory(doc, c_node)
|
3233
|
+
|
3234
|
+
|
3235
|
+
def ProcessingInstruction(target, text=None):
|
3236
|
+
"""ProcessingInstruction(target, text=None)
|
3237
|
+
|
3238
|
+
ProcessingInstruction element factory. This factory function creates a
|
3239
|
+
special element that will be serialized as an XML processing instruction.
|
3240
|
+
"""
|
3241
|
+
cdef _Document doc
|
3242
|
+
cdef xmlNode* c_node
|
3243
|
+
cdef xmlDoc* c_doc
|
3244
|
+
|
3245
|
+
target = _utf8(target)
|
3246
|
+
_tagValidOrRaise(target)
|
3247
|
+
if target.lower() == b'xml':
|
3248
|
+
raise ValueError, f"Invalid PI name '{target}'"
|
3249
|
+
|
3250
|
+
if text is None:
|
3251
|
+
text = b''
|
3252
|
+
else:
|
3253
|
+
text = _utf8(text)
|
3254
|
+
if b'?>' in text:
|
3255
|
+
raise ValueError, "PI text must not contain '?>'"
|
3256
|
+
|
3257
|
+
c_doc = _newXMLDoc()
|
3258
|
+
doc = _documentFactory(c_doc, None)
|
3259
|
+
c_node = _createPI(c_doc, _xcstr(target), _xcstr(text))
|
3260
|
+
tree.xmlAddChild(<xmlNode*>c_doc, c_node)
|
3261
|
+
return _elementFactory(doc, c_node)
|
3262
|
+
|
3263
|
+
PI = ProcessingInstruction
|
3264
|
+
|
3265
|
+
|
3266
|
+
cdef class CDATA:
|
3267
|
+
"""CDATA(data)
|
3268
|
+
|
3269
|
+
CDATA factory. This factory creates an opaque data object that
|
3270
|
+
can be used to set Element text. The usual way to use it is::
|
3271
|
+
|
3272
|
+
>>> el = Element('content')
|
3273
|
+
>>> el.text = CDATA('a string')
|
3274
|
+
|
3275
|
+
>>> print(el.text)
|
3276
|
+
a string
|
3277
|
+
>>> print(tostring(el, encoding="unicode"))
|
3278
|
+
<content><![CDATA[a string]]></content>
|
3279
|
+
"""
|
3280
|
+
cdef bytes _utf8_data
|
3281
|
+
def __cinit__(self, data):
|
3282
|
+
self._utf8_data = _utf8(data)
|
3283
|
+
|
3284
|
+
|
3285
|
+
def Entity(name):
|
3286
|
+
"""Entity(name)
|
3287
|
+
|
3288
|
+
Entity factory. This factory function creates a special element
|
3289
|
+
that will be serialized as an XML entity reference or character
|
3290
|
+
reference. Note, however, that entities will not be automatically
|
3291
|
+
declared in the document. A document that uses entity references
|
3292
|
+
requires a DTD to define the entities.
|
3293
|
+
"""
|
3294
|
+
cdef _Document doc
|
3295
|
+
cdef xmlNode* c_node
|
3296
|
+
cdef xmlDoc* c_doc
|
3297
|
+
name_utf = _utf8(name)
|
3298
|
+
c_name = _xcstr(name_utf)
|
3299
|
+
if c_name[0] == c'#':
|
3300
|
+
if not _characterReferenceIsValid(c_name + 1):
|
3301
|
+
raise ValueError, f"Invalid character reference: '{name}'"
|
3302
|
+
elif not _xmlNameIsValid(c_name):
|
3303
|
+
raise ValueError, f"Invalid entity reference: '{name}'"
|
3304
|
+
c_doc = _newXMLDoc()
|
3305
|
+
doc = _documentFactory(c_doc, None)
|
3306
|
+
c_node = _createEntity(c_doc, c_name)
|
3307
|
+
tree.xmlAddChild(<xmlNode*>c_doc, c_node)
|
3308
|
+
return _elementFactory(doc, c_node)
|
3309
|
+
|
3310
|
+
|
3311
|
+
def SubElement(_Element _parent not None, _tag,
|
3312
|
+
attrib=None, nsmap=None, **_extra):
|
3313
|
+
"""SubElement(_parent, _tag, attrib=None, nsmap=None, **_extra)
|
3314
|
+
|
3315
|
+
Subelement factory. This function creates an element instance, and
|
3316
|
+
appends it to an existing element.
|
3317
|
+
"""
|
3318
|
+
return _makeSubElement(_parent, _tag, None, None, attrib, nsmap, _extra)
|
3319
|
+
|
3320
|
+
from typing import Generic, TypeVar
|
3321
|
+
|
3322
|
+
T = TypeVar("T")
|
3323
|
+
|
3324
|
+
class ElementTree(ABC, Generic[T]):
|
3325
|
+
def __new__(cls, _Element element=None, *, file=None, _BaseParser parser=None):
|
3326
|
+
"""ElementTree(element=None, file=None, parser=None)
|
3327
|
+
|
3328
|
+
ElementTree wrapper class.
|
3329
|
+
"""
|
3330
|
+
cdef xmlNode* c_next
|
3331
|
+
cdef xmlNode* c_node
|
3332
|
+
cdef xmlNode* c_node_copy
|
3333
|
+
cdef xmlDoc* c_doc
|
3334
|
+
cdef _ElementTree etree
|
3335
|
+
cdef _Document doc
|
3336
|
+
|
3337
|
+
if element is not None:
|
3338
|
+
doc = element._doc
|
3339
|
+
elif file is not None:
|
3340
|
+
try:
|
3341
|
+
doc = _parseDocument(file, parser, None)
|
3342
|
+
except _TargetParserResult as result_container:
|
3343
|
+
return result_container.result
|
3344
|
+
else:
|
3345
|
+
c_doc = _newXMLDoc()
|
3346
|
+
doc = _documentFactory(c_doc, parser)
|
3347
|
+
|
3348
|
+
return _elementTreeFactory(doc, element)
|
3349
|
+
|
3350
|
+
# Register _ElementTree as a virtual subclass of ElementTree
|
3351
|
+
ElementTree.register(_ElementTree)
|
3352
|
+
|
3353
|
+
# Remove "ABC" and typing helpers from module dict
|
3354
|
+
del ABC, Generic, TypeVar, T
|
3355
|
+
|
3356
|
+
def HTML(text, _BaseParser parser=None, *, base_url=None):
|
3357
|
+
"""HTML(text, parser=None, base_url=None)
|
3358
|
+
|
3359
|
+
Parses an HTML document from a string constant. Returns the root
|
3360
|
+
node (or the result returned by a parser target). This function
|
3361
|
+
can be used to embed "HTML literals" in Python code.
|
3362
|
+
|
3363
|
+
To override the parser with a different ``HTMLParser`` you can pass it to
|
3364
|
+
the ``parser`` keyword argument.
|
3365
|
+
|
3366
|
+
The ``base_url`` keyword argument allows to set the original base URL of
|
3367
|
+
the document to support relative Paths when looking up external entities
|
3368
|
+
(DTD, XInclude, ...).
|
3369
|
+
"""
|
3370
|
+
cdef _Document doc
|
3371
|
+
if parser is None:
|
3372
|
+
parser = __GLOBAL_PARSER_CONTEXT.getDefaultParser()
|
3373
|
+
if not isinstance(parser, HTMLParser):
|
3374
|
+
parser = __DEFAULT_HTML_PARSER
|
3375
|
+
try:
|
3376
|
+
doc = _parseMemoryDocument(text, base_url, parser)
|
3377
|
+
return doc.getroot()
|
3378
|
+
except _TargetParserResult as result_container:
|
3379
|
+
return result_container.result
|
3380
|
+
|
3381
|
+
|
3382
|
+
def XML(text, _BaseParser parser=None, *, base_url=None):
|
3383
|
+
"""XML(text, parser=None, base_url=None)
|
3384
|
+
|
3385
|
+
Parses an XML document or fragment from a string constant.
|
3386
|
+
Returns the root node (or the result returned by a parser target).
|
3387
|
+
This function can be used to embed "XML literals" in Python code,
|
3388
|
+
like in
|
3389
|
+
|
3390
|
+
>>> root = XML("<root><test/></root>")
|
3391
|
+
>>> print(root.tag)
|
3392
|
+
root
|
3393
|
+
|
3394
|
+
To override the parser with a different ``XMLParser`` you can pass it to
|
3395
|
+
the ``parser`` keyword argument.
|
3396
|
+
|
3397
|
+
The ``base_url`` keyword argument allows to set the original base URL of
|
3398
|
+
the document to support relative Paths when looking up external entities
|
3399
|
+
(DTD, XInclude, ...).
|
3400
|
+
"""
|
3401
|
+
cdef _Document doc
|
3402
|
+
if parser is None:
|
3403
|
+
parser = __GLOBAL_PARSER_CONTEXT.getDefaultParser()
|
3404
|
+
if not isinstance(parser, XMLParser):
|
3405
|
+
parser = __DEFAULT_XML_PARSER
|
3406
|
+
try:
|
3407
|
+
doc = _parseMemoryDocument(text, base_url, parser)
|
3408
|
+
return doc.getroot()
|
3409
|
+
except _TargetParserResult as result_container:
|
3410
|
+
return result_container.result
|
3411
|
+
|
3412
|
+
|
3413
|
+
def fromstring(text, _BaseParser parser=None, *, base_url=None):
|
3414
|
+
"""fromstring(text, parser=None, base_url=None)
|
3415
|
+
|
3416
|
+
Parses an XML document or fragment from a string. Returns the
|
3417
|
+
root node (or the result returned by a parser target).
|
3418
|
+
|
3419
|
+
To override the default parser with a different parser you can pass it to
|
3420
|
+
the ``parser`` keyword argument.
|
3421
|
+
|
3422
|
+
The ``base_url`` keyword argument allows to set the original base URL of
|
3423
|
+
the document to support relative Paths when looking up external entities
|
3424
|
+
(DTD, XInclude, ...).
|
3425
|
+
"""
|
3426
|
+
cdef _Document doc
|
3427
|
+
try:
|
3428
|
+
doc = _parseMemoryDocument(text, base_url, parser)
|
3429
|
+
return doc.getroot()
|
3430
|
+
except _TargetParserResult as result_container:
|
3431
|
+
return result_container.result
|
3432
|
+
|
3433
|
+
|
3434
|
+
def fromstringlist(strings, _BaseParser parser=None):
|
3435
|
+
"""fromstringlist(strings, parser=None)
|
3436
|
+
|
3437
|
+
Parses an XML document from a sequence of strings. Returns the
|
3438
|
+
root node (or the result returned by a parser target).
|
3439
|
+
|
3440
|
+
To override the default parser with a different parser you can pass it to
|
3441
|
+
the ``parser`` keyword argument.
|
3442
|
+
"""
|
3443
|
+
cdef _Document doc
|
3444
|
+
if isinstance(strings, (bytes, unicode)):
|
3445
|
+
raise ValueError("passing a single string into fromstringlist() is not"
|
3446
|
+
" efficient, use fromstring() instead")
|
3447
|
+
if parser is None:
|
3448
|
+
parser = __GLOBAL_PARSER_CONTEXT.getDefaultParser()
|
3449
|
+
feed = parser.feed
|
3450
|
+
for data in strings:
|
3451
|
+
feed(data)
|
3452
|
+
return parser.close()
|
3453
|
+
|
3454
|
+
|
3455
|
+
def iselement(element):
|
3456
|
+
"""iselement(element)
|
3457
|
+
|
3458
|
+
Checks if an object appears to be a valid element object.
|
3459
|
+
"""
|
3460
|
+
return isinstance(element, _Element) and (<_Element>element)._c_node is not NULL
|
3461
|
+
|
3462
|
+
|
3463
|
+
def indent(tree, space=" ", *, Py_ssize_t level=0):
|
3464
|
+
"""indent(tree, space=" ", level=0)
|
3465
|
+
|
3466
|
+
Indent an XML document by inserting newlines and indentation space
|
3467
|
+
after elements.
|
3468
|
+
|
3469
|
+
*tree* is the ElementTree or Element to modify. The (root) element
|
3470
|
+
itself will not be changed, but the tail text of all elements in its
|
3471
|
+
subtree will be adapted.
|
3472
|
+
|
3473
|
+
*space* is the whitespace to insert for each indentation level, two
|
3474
|
+
space characters by default.
|
3475
|
+
|
3476
|
+
*level* is the initial indentation level. Setting this to a higher
|
3477
|
+
value than 0 can be used for indenting subtrees that are more deeply
|
3478
|
+
nested inside of a document.
|
3479
|
+
"""
|
3480
|
+
root = _rootNodeOrRaise(tree)
|
3481
|
+
if level < 0:
|
3482
|
+
raise ValueError(f"Initial indentation level must be >= 0, got {level}")
|
3483
|
+
if _hasChild(root._c_node):
|
3484
|
+
space = _utf8(space)
|
3485
|
+
indent = b"\n" + level * space
|
3486
|
+
_indent_children(root._c_node, 1, space, [indent, indent + space])
|
3487
|
+
|
3488
|
+
|
3489
|
+
cdef int _indent_children(xmlNode* c_node, Py_ssize_t level, bytes one_space, list indentations) except -1:
|
3490
|
+
# Reuse indentation strings for speed.
|
3491
|
+
if len(indentations) <= level:
|
3492
|
+
indentations.append(indentations[-1] + one_space)
|
3493
|
+
|
3494
|
+
# Start a new indentation level for the first child.
|
3495
|
+
child_indentation = indentations[level]
|
3496
|
+
if not _hasNonWhitespaceText(c_node):
|
3497
|
+
_setNodeText(c_node, child_indentation)
|
3498
|
+
|
3499
|
+
# Recursively indent all children.
|
3500
|
+
cdef xmlNode* c_child = _findChildForwards(c_node, 0)
|
3501
|
+
while c_child is not NULL:
|
3502
|
+
if _hasChild(c_child):
|
3503
|
+
_indent_children(c_child, level+1, one_space, indentations)
|
3504
|
+
c_next_child = _nextElement(c_child)
|
3505
|
+
if not _hasNonWhitespaceTail(c_child):
|
3506
|
+
if c_next_child is NULL:
|
3507
|
+
# Dedent after the last child.
|
3508
|
+
child_indentation = indentations[level-1]
|
3509
|
+
_setTailText(c_child, child_indentation)
|
3510
|
+
c_child = c_next_child
|
3511
|
+
return 0
|
3512
|
+
|
3513
|
+
|
3514
|
+
def dump(_Element elem not None, *, bint pretty_print=True, bint with_tail=True):
|
3515
|
+
"""dump(elem, pretty_print=True, with_tail=True)
|
3516
|
+
|
3517
|
+
Writes an element tree or element structure to sys.stdout. This function
|
3518
|
+
should be used for debugging only.
|
3519
|
+
"""
|
3520
|
+
xml = tostring(elem, pretty_print=pretty_print, with_tail=with_tail, encoding='unicode')
|
3521
|
+
if not pretty_print:
|
3522
|
+
xml += '\n'
|
3523
|
+
sys.stdout.write(xml)
|
3524
|
+
|
3525
|
+
|
3526
|
+
def tostring(element_or_tree, *, encoding=None, method="xml",
|
3527
|
+
xml_declaration=None, bint pretty_print=False, bint with_tail=True,
|
3528
|
+
standalone=None, doctype=None,
|
3529
|
+
# method='c14n'
|
3530
|
+
bint exclusive=False, inclusive_ns_prefixes=None,
|
3531
|
+
# method='c14n2'
|
3532
|
+
bint with_comments=True, bint strip_text=False,
|
3533
|
+
):
|
3534
|
+
"""tostring(element_or_tree, encoding=None, method="xml",
|
3535
|
+
xml_declaration=None, pretty_print=False, with_tail=True,
|
3536
|
+
standalone=None, doctype=None,
|
3537
|
+
exclusive=False, inclusive_ns_prefixes=None,
|
3538
|
+
with_comments=True, strip_text=False,
|
3539
|
+
)
|
3540
|
+
|
3541
|
+
Serialize an element to an encoded string representation of its XML
|
3542
|
+
tree.
|
3543
|
+
|
3544
|
+
Defaults to ASCII encoding without XML declaration. This
|
3545
|
+
behaviour can be configured with the keyword arguments 'encoding'
|
3546
|
+
(string) and 'xml_declaration' (bool). Note that changing the
|
3547
|
+
encoding to a non UTF-8 compatible encoding will enable a
|
3548
|
+
declaration by default.
|
3549
|
+
|
3550
|
+
You can also serialise to a Unicode string without declaration by
|
3551
|
+
passing the name ``'unicode'`` as encoding (or the ``str`` function
|
3552
|
+
in Py3 or ``unicode`` in Py2). This changes the return value from
|
3553
|
+
a byte string to an unencoded unicode string.
|
3554
|
+
|
3555
|
+
The keyword argument 'pretty_print' (bool) enables formatted XML.
|
3556
|
+
|
3557
|
+
The keyword argument 'method' selects the output method: 'xml',
|
3558
|
+
'html', plain 'text' (text content without tags), 'c14n' or 'c14n2'.
|
3559
|
+
Default is 'xml'.
|
3560
|
+
|
3561
|
+
With ``method="c14n"`` (C14N version 1), the options ``exclusive``,
|
3562
|
+
``with_comments`` and ``inclusive_ns_prefixes`` request exclusive
|
3563
|
+
C14N, include comments, and list the inclusive prefixes respectively.
|
3564
|
+
|
3565
|
+
With ``method="c14n2"`` (C14N version 2), the ``with_comments`` and
|
3566
|
+
``strip_text`` options control the output of comments and text space
|
3567
|
+
according to C14N 2.0.
|
3568
|
+
|
3569
|
+
Passing a boolean value to the ``standalone`` option will output
|
3570
|
+
an XML declaration with the corresponding ``standalone`` flag.
|
3571
|
+
|
3572
|
+
The ``doctype`` option allows passing in a plain string that will
|
3573
|
+
be serialised before the XML tree. Note that passing in non
|
3574
|
+
well-formed content here will make the XML output non well-formed.
|
3575
|
+
Also, an existing doctype in the document tree will not be removed
|
3576
|
+
when serialising an ElementTree instance.
|
3577
|
+
|
3578
|
+
You can prevent the tail text of the element from being serialised
|
3579
|
+
by passing the boolean ``with_tail`` option. This has no impact
|
3580
|
+
on the tail text of children, which will always be serialised.
|
3581
|
+
"""
|
3582
|
+
cdef bint write_declaration
|
3583
|
+
cdef int is_standalone
|
3584
|
+
# C14N serialisation
|
3585
|
+
if method in ('c14n', 'c14n2'):
|
3586
|
+
if encoding is not None:
|
3587
|
+
raise ValueError("Cannot specify encoding with C14N")
|
3588
|
+
if xml_declaration:
|
3589
|
+
raise ValueError("Cannot enable XML declaration in C14N")
|
3590
|
+
if method == 'c14n':
|
3591
|
+
return _tostringC14N(element_or_tree, exclusive, with_comments, inclusive_ns_prefixes)
|
3592
|
+
else:
|
3593
|
+
out = BytesIO()
|
3594
|
+
target = C14NWriterTarget(
|
3595
|
+
utf8_writer(out).write,
|
3596
|
+
with_comments=with_comments, strip_text=strip_text)
|
3597
|
+
_tree_to_target(element_or_tree, target)
|
3598
|
+
return out.getvalue()
|
3599
|
+
if not with_comments:
|
3600
|
+
raise ValueError("Can only discard comments in C14N serialisation")
|
3601
|
+
if strip_text:
|
3602
|
+
raise ValueError("Can only strip text in C14N 2.0 serialisation")
|
3603
|
+
if encoding is unicode or (encoding is not None and encoding.lower() == 'unicode'):
|
3604
|
+
if xml_declaration:
|
3605
|
+
raise ValueError, \
|
3606
|
+
"Serialisation to unicode must not request an XML declaration"
|
3607
|
+
write_declaration = 0
|
3608
|
+
encoding = unicode
|
3609
|
+
elif xml_declaration is None:
|
3610
|
+
# by default, write an XML declaration only for non-standard encodings
|
3611
|
+
write_declaration = encoding is not None and encoding.upper() not in \
|
3612
|
+
('ASCII', 'UTF-8', 'UTF8', 'US-ASCII')
|
3613
|
+
else:
|
3614
|
+
write_declaration = xml_declaration
|
3615
|
+
if encoding is None:
|
3616
|
+
encoding = 'ASCII'
|
3617
|
+
if standalone is None:
|
3618
|
+
is_standalone = -1
|
3619
|
+
elif standalone:
|
3620
|
+
write_declaration = 1
|
3621
|
+
is_standalone = 1
|
3622
|
+
else:
|
3623
|
+
write_declaration = 1
|
3624
|
+
is_standalone = 0
|
3625
|
+
|
3626
|
+
if isinstance(element_or_tree, _Element):
|
3627
|
+
return _tostring(<_Element>element_or_tree, encoding, doctype, method,
|
3628
|
+
write_declaration, 0, pretty_print, with_tail,
|
3629
|
+
is_standalone)
|
3630
|
+
elif isinstance(element_or_tree, _ElementTree):
|
3631
|
+
return _tostring((<_ElementTree>element_or_tree)._context_node,
|
3632
|
+
encoding, doctype, method, write_declaration, 1,
|
3633
|
+
pretty_print, with_tail, is_standalone)
|
3634
|
+
else:
|
3635
|
+
raise TypeError, f"Type '{python._fqtypename(element_or_tree).decode('utf8')}' cannot be serialized."
|
3636
|
+
|
3637
|
+
|
3638
|
+
|
3639
|
+
def tostringlist(element_or_tree, *args, **kwargs):
|
3640
|
+
"""tostringlist(element_or_tree, *args, **kwargs)
|
3641
|
+
|
3642
|
+
Serialize an element to an encoded string representation of its XML
|
3643
|
+
tree, stored in a list of partial strings.
|
3644
|
+
|
3645
|
+
This is purely for ElementTree 1.3 compatibility. The result is a
|
3646
|
+
single string wrapped in a list.
|
3647
|
+
"""
|
3648
|
+
return [tostring(element_or_tree, *args, **kwargs)]
|
3649
|
+
|
3650
|
+
|
3651
|
+
def tounicode(element_or_tree, *, method="xml", bint pretty_print=False,
|
3652
|
+
bint with_tail=True, doctype=None):
|
3653
|
+
"""tounicode(element_or_tree, method="xml", pretty_print=False,
|
3654
|
+
with_tail=True, doctype=None)
|
3655
|
+
|
3656
|
+
Serialize an element to the Python unicode representation of its XML
|
3657
|
+
tree.
|
3658
|
+
|
3659
|
+
:deprecated: use ``tostring(el, encoding='unicode')`` instead.
|
3660
|
+
|
3661
|
+
Note that the result does not carry an XML encoding declaration and is
|
3662
|
+
therefore not necessarily suited for serialization to byte streams without
|
3663
|
+
further treatment.
|
3664
|
+
|
3665
|
+
The boolean keyword argument 'pretty_print' enables formatted XML.
|
3666
|
+
|
3667
|
+
The keyword argument 'method' selects the output method: 'xml',
|
3668
|
+
'html' or plain 'text'.
|
3669
|
+
|
3670
|
+
You can prevent the tail text of the element from being serialised
|
3671
|
+
by passing the boolean ``with_tail`` option. This has no impact
|
3672
|
+
on the tail text of children, which will always be serialised.
|
3673
|
+
"""
|
3674
|
+
if isinstance(element_or_tree, _Element):
|
3675
|
+
return _tostring(<_Element>element_or_tree, unicode, doctype, method,
|
3676
|
+
0, 0, pretty_print, with_tail, -1)
|
3677
|
+
elif isinstance(element_or_tree, _ElementTree):
|
3678
|
+
return _tostring((<_ElementTree>element_or_tree)._context_node,
|
3679
|
+
unicode, doctype, method, 0, 1, pretty_print,
|
3680
|
+
with_tail, -1)
|
3681
|
+
else:
|
3682
|
+
raise TypeError, f"Type '{type(element_or_tree)}' cannot be serialized."
|
3683
|
+
|
3684
|
+
|
3685
|
+
def parse(source, _BaseParser parser=None, *, base_url=None):
|
3686
|
+
"""parse(source, parser=None, base_url=None)
|
3687
|
+
|
3688
|
+
Return an ElementTree object loaded with source elements. If no parser
|
3689
|
+
is provided as second argument, the default parser is used.
|
3690
|
+
|
3691
|
+
The ``source`` can be any of the following:
|
3692
|
+
|
3693
|
+
- a file name/path
|
3694
|
+
- a file object
|
3695
|
+
- a file-like object
|
3696
|
+
- a URL using the HTTP or FTP protocol
|
3697
|
+
|
3698
|
+
To parse from a string, use the ``fromstring()`` function instead.
|
3699
|
+
|
3700
|
+
Note that it is generally faster to parse from a file path or URL
|
3701
|
+
than from an open file object or file-like object. Transparent
|
3702
|
+
decompression from gzip compressed sources is supported (unless
|
3703
|
+
explicitly disabled in libxml2).
|
3704
|
+
|
3705
|
+
The ``base_url`` keyword allows setting a URL for the document
|
3706
|
+
when parsing from a file-like object. This is needed when looking
|
3707
|
+
up external entities (DTD, XInclude, ...) with relative paths.
|
3708
|
+
"""
|
3709
|
+
cdef _Document doc
|
3710
|
+
try:
|
3711
|
+
doc = _parseDocument(source, parser, base_url)
|
3712
|
+
return _elementTreeFactory(doc, None)
|
3713
|
+
except _TargetParserResult as result_container:
|
3714
|
+
return result_container.result
|
3715
|
+
|
3716
|
+
|
3717
|
+
def adopt_external_document(capsule, _BaseParser parser=None):
|
3718
|
+
"""adopt_external_document(capsule, parser=None)
|
3719
|
+
|
3720
|
+
Unpack a libxml2 document pointer from a PyCapsule and wrap it in an
|
3721
|
+
lxml ElementTree object.
|
3722
|
+
|
3723
|
+
This allows external libraries to build XML/HTML trees using libxml2
|
3724
|
+
and then pass them efficiently into lxml for further processing.
|
3725
|
+
|
3726
|
+
If a ``parser`` is provided, it will be used for configuring the
|
3727
|
+
lxml document. No parsing will be done.
|
3728
|
+
|
3729
|
+
The capsule must have the name ``"libxml2:xmlDoc"`` and its pointer
|
3730
|
+
value must reference a correct libxml2 document of type ``xmlDoc*``.
|
3731
|
+
The creator of the capsule must take care to correctly clean up the
|
3732
|
+
document using an appropriate capsule destructor. By default, the
|
3733
|
+
libxml2 document will be copied to let lxml safely own the memory
|
3734
|
+
of the internal tree that it uses.
|
3735
|
+
|
3736
|
+
If the capsule context is non-NULL, it must point to a C string that
|
3737
|
+
can be compared using ``strcmp()``. If the context string equals
|
3738
|
+
``"destructor:xmlFreeDoc"``, the libxml2 document will not be copied
|
3739
|
+
but the capsule invalidated instead by clearing its destructor and
|
3740
|
+
name. That way, lxml takes ownership of the libxml2 document in memory
|
3741
|
+
without creating a copy first, and the capsule destructor will not be
|
3742
|
+
called. The document will then eventually be cleaned up by lxml using
|
3743
|
+
the libxml2 API function ``xmlFreeDoc()`` once it is no longer used.
|
3744
|
+
|
3745
|
+
If no copy is made, later modifications of the tree outside of lxml
|
3746
|
+
should not be attempted after transferring the ownership.
|
3747
|
+
"""
|
3748
|
+
cdef xmlDoc* c_doc
|
3749
|
+
cdef bint is_owned = False
|
3750
|
+
c_doc = <xmlDoc*> python.lxml_unpack_xmldoc_capsule(capsule, &is_owned)
|
3751
|
+
doc = _adoptForeignDoc(c_doc, parser, is_owned)
|
3752
|
+
return _elementTreeFactory(doc, None)
|
3753
|
+
|
3754
|
+
|
3755
|
+
################################################################################
|
3756
|
+
# Include submodules
|
3757
|
+
|
3758
|
+
include "readonlytree.pxi" # Read-only implementation of Element proxies
|
3759
|
+
include "classlookup.pxi" # Element class lookup mechanisms
|
3760
|
+
include "nsclasses.pxi" # Namespace implementation and registry
|
3761
|
+
include "docloader.pxi" # Support for custom document loaders
|
3762
|
+
include "parser.pxi" # XML and HTML parsers
|
3763
|
+
include "saxparser.pxi" # SAX-like Parser interface and tree builder
|
3764
|
+
include "parsertarget.pxi" # ET Parser target
|
3765
|
+
include "serializer.pxi" # XML output functions
|
3766
|
+
include "iterparse.pxi" # incremental XML parsing
|
3767
|
+
include "xmlid.pxi" # XMLID and IDDict
|
3768
|
+
include "xinclude.pxi" # XInclude
|
3769
|
+
include "cleanup.pxi" # Cleanup and recursive element removal functions
|
3770
|
+
|
3771
|
+
|
3772
|
+
################################################################################
|
3773
|
+
# Include submodules for XPath and XSLT
|
3774
|
+
|
3775
|
+
include "extensions.pxi" # XPath/XSLT extension functions
|
3776
|
+
include "xpath.pxi" # XPath evaluation
|
3777
|
+
include "xslt.pxi" # XSL transformations
|
3778
|
+
include "xsltext.pxi" # XSL extension elements
|
3779
|
+
|
3780
|
+
|
3781
|
+
################################################################################
|
3782
|
+
# Validation
|
3783
|
+
|
3784
|
+
cdef class DocumentInvalid(LxmlError):
|
3785
|
+
"""Validation error.
|
3786
|
+
|
3787
|
+
Raised by all document validators when their ``assertValid(tree)``
|
3788
|
+
method fails.
|
3789
|
+
"""
|
3790
|
+
|
3791
|
+
|
3792
|
+
cdef class _Validator:
|
3793
|
+
"Base class for XML validators."
|
3794
|
+
cdef _ErrorLog _error_log
|
3795
|
+
def __cinit__(self):
|
3796
|
+
self._error_log = _ErrorLog()
|
3797
|
+
|
3798
|
+
def validate(self, etree):
|
3799
|
+
"""validate(self, etree)
|
3800
|
+
|
3801
|
+
Validate the document using this schema.
|
3802
|
+
|
3803
|
+
Returns true if document is valid, false if not.
|
3804
|
+
"""
|
3805
|
+
return self(etree)
|
3806
|
+
|
3807
|
+
def assertValid(self, etree):
|
3808
|
+
"""assertValid(self, etree)
|
3809
|
+
|
3810
|
+
Raises `DocumentInvalid` if the document does not comply with the schema.
|
3811
|
+
"""
|
3812
|
+
if not self(etree):
|
3813
|
+
raise DocumentInvalid(self._error_log._buildExceptionMessage(
|
3814
|
+
"Document does not comply with schema"),
|
3815
|
+
self._error_log)
|
3816
|
+
|
3817
|
+
def assert_(self, etree):
|
3818
|
+
"""assert_(self, etree)
|
3819
|
+
|
3820
|
+
Raises `AssertionError` if the document does not comply with the schema.
|
3821
|
+
"""
|
3822
|
+
if not self(etree):
|
3823
|
+
raise AssertionError, self._error_log._buildExceptionMessage(
|
3824
|
+
"Document does not comply with schema")
|
3825
|
+
|
3826
|
+
cpdef _append_log_message(self, int domain, int type, int level, int line,
|
3827
|
+
message, filename):
|
3828
|
+
self._error_log._receiveGeneric(domain, type, level, line, message,
|
3829
|
+
filename)
|
3830
|
+
|
3831
|
+
cpdef _clear_error_log(self):
|
3832
|
+
self._error_log.clear()
|
3833
|
+
|
3834
|
+
@property
|
3835
|
+
def error_log(self):
|
3836
|
+
"""The log of validation errors and warnings."""
|
3837
|
+
assert self._error_log is not None, "XPath evaluator not initialised"
|
3838
|
+
return self._error_log.copy()
|
3839
|
+
|
3840
|
+
include "dtd.pxi" # DTD
|
3841
|
+
include "relaxng.pxi" # RelaxNG
|
3842
|
+
include "xmlschema.pxi" # XMLSchema
|
3843
|
+
include "schematron.pxi" # Schematron (requires libxml2 2.6.21+)
|
3844
|
+
|
3845
|
+
################################################################################
|
3846
|
+
# Public C API
|
3847
|
+
|
3848
|
+
include "public-api.pxi"
|
3849
|
+
|
3850
|
+
################################################################################
|
3851
|
+
# Other stuff
|
3852
|
+
|
3853
|
+
include "debug.pxi"
|