ยปCore Development>Code coverage>Lib/xml/etree/ElementPath.py

Python code coverage for Lib/xml/etree/ElementPath.py

#countcontent
1n/a#
2n/a# ElementTree
3n/a# $Id: ElementPath.py 3375 2008-02-13 08:05:08Z fredrik $
4n/a#
5n/a# limited xpath support for element trees
6n/a#
7n/a# history:
8n/a# 2003-05-23 fl created
9n/a# 2003-05-28 fl added support for // etc
10n/a# 2003-08-27 fl fixed parsing of periods in element names
11n/a# 2007-09-10 fl new selection engine
12n/a# 2007-09-12 fl fixed parent selector
13n/a# 2007-09-13 fl added iterfind; changed findall to return a list
14n/a# 2007-11-30 fl added namespaces support
15n/a# 2009-10-30 fl added child element value filter
16n/a#
17n/a# Copyright (c) 2003-2009 by Fredrik Lundh. All rights reserved.
18n/a#
19n/a# fredrik@pythonware.com
20n/a# http://www.pythonware.com
21n/a#
22n/a# --------------------------------------------------------------------
23n/a# The ElementTree toolkit is
24n/a#
25n/a# Copyright (c) 1999-2009 by Fredrik Lundh
26n/a#
27n/a# By obtaining, using, and/or copying this software and/or its
28n/a# associated documentation, you agree that you have read, understood,
29n/a# and will comply with the following terms and conditions:
30n/a#
31n/a# Permission to use, copy, modify, and distribute this software and
32n/a# its associated documentation for any purpose and without fee is
33n/a# hereby granted, provided that the above copyright notice appears in
34n/a# all copies, and that both that copyright notice and this permission
35n/a# notice appear in supporting documentation, and that the name of
36n/a# Secret Labs AB or the author not be used in advertising or publicity
37n/a# pertaining to distribution of the software without specific, written
38n/a# prior permission.
39n/a#
40n/a# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
41n/a# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
42n/a# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
43n/a# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
44n/a# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
45n/a# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
46n/a# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
47n/a# OF THIS SOFTWARE.
48n/a# --------------------------------------------------------------------
49n/a
50n/a# Licensed to PSF under a Contributor Agreement.
51n/a# See http://www.python.org/psf/license for licensing details.
52n/a
53n/a##
54n/a# Implementation module for XPath support. There's usually no reason
55n/a# to import this module directly; the <b>ElementTree</b> does this for
56n/a# you, if needed.
57n/a##
58n/a
59n/aimport re
60n/a
61n/axpath_tokenizer_re = re.compile(
62n/a r"("
63n/a r"'[^']*'|\"[^\"]*\"|"
64n/a r"::|"
65n/a r"//?|"
66n/a r"\.\.|"
67n/a r"\(\)|"
68n/a r"[/.*:\[\]\(\)@=])|"
69n/a r"((?:\{[^}]+\})?[^/\[\]\(\)@=\s]+)|"
70n/a r"\s+"
71n/a )
72n/a
73n/adef xpath_tokenizer(pattern, namespaces=None):
74n/a for token in xpath_tokenizer_re.findall(pattern):
75n/a tag = token[1]
76n/a if tag and tag[0] != "{" and ":" in tag:
77n/a try:
78n/a prefix, uri = tag.split(":", 1)
79n/a if not namespaces:
80n/a raise KeyError
81n/a yield token[0], "{%s}%s" % (namespaces[prefix], uri)
82n/a except KeyError:
83n/a raise SyntaxError("prefix %r not found in prefix map" % prefix)
84n/a else:
85n/a yield token
86n/a
87n/adef get_parent_map(context):
88n/a parent_map = context.parent_map
89n/a if parent_map is None:
90n/a context.parent_map = parent_map = {}
91n/a for p in context.root.iter():
92n/a for e in p:
93n/a parent_map[e] = p
94n/a return parent_map
95n/a
96n/adef prepare_child(next, token):
97n/a tag = token[1]
98n/a def select(context, result):
99n/a for elem in result:
100n/a for e in elem:
101n/a if e.tag == tag:
102n/a yield e
103n/a return select
104n/a
105n/adef prepare_star(next, token):
106n/a def select(context, result):
107n/a for elem in result:
108n/a yield from elem
109n/a return select
110n/a
111n/adef prepare_self(next, token):
112n/a def select(context, result):
113n/a yield from result
114n/a return select
115n/a
116n/adef prepare_descendant(next, token):
117n/a try:
118n/a token = next()
119n/a except StopIteration:
120n/a return
121n/a if token[0] == "*":
122n/a tag = "*"
123n/a elif not token[0]:
124n/a tag = token[1]
125n/a else:
126n/a raise SyntaxError("invalid descendant")
127n/a def select(context, result):
128n/a for elem in result:
129n/a for e in elem.iter(tag):
130n/a if e is not elem:
131n/a yield e
132n/a return select
133n/a
134n/adef prepare_parent(next, token):
135n/a def select(context, result):
136n/a # FIXME: raise error if .. is applied at toplevel?
137n/a parent_map = get_parent_map(context)
138n/a result_map = {}
139n/a for elem in result:
140n/a if elem in parent_map:
141n/a parent = parent_map[elem]
142n/a if parent not in result_map:
143n/a result_map[parent] = None
144n/a yield parent
145n/a return select
146n/a
147n/adef prepare_predicate(next, token):
148n/a # FIXME: replace with real parser!!! refs:
149n/a # http://effbot.org/zone/simple-iterator-parser.htm
150n/a # http://javascript.crockford.com/tdop/tdop.html
151n/a signature = []
152n/a predicate = []
153n/a while 1:
154n/a try:
155n/a token = next()
156n/a except StopIteration:
157n/a return
158n/a if token[0] == "]":
159n/a break
160n/a if token[0] and token[0][:1] in "'\"":
161n/a token = "'", token[0][1:-1]
162n/a signature.append(token[0] or "-")
163n/a predicate.append(token[1])
164n/a signature = "".join(signature)
165n/a # use signature to determine predicate type
166n/a if signature == "@-":
167n/a # [@attribute] predicate
168n/a key = predicate[1]
169n/a def select(context, result):
170n/a for elem in result:
171n/a if elem.get(key) is not None:
172n/a yield elem
173n/a return select
174n/a if signature == "@-='":
175n/a # [@attribute='value']
176n/a key = predicate[1]
177n/a value = predicate[-1]
178n/a def select(context, result):
179n/a for elem in result:
180n/a if elem.get(key) == value:
181n/a yield elem
182n/a return select
183n/a if signature == "-" and not re.match(r"\-?\d+$", predicate[0]):
184n/a # [tag]
185n/a tag = predicate[0]
186n/a def select(context, result):
187n/a for elem in result:
188n/a if elem.find(tag) is not None:
189n/a yield elem
190n/a return select
191n/a if signature == "-='" and not re.match(r"\-?\d+$", predicate[0]):
192n/a # [tag='value']
193n/a tag = predicate[0]
194n/a value = predicate[-1]
195n/a def select(context, result):
196n/a for elem in result:
197n/a for e in elem.findall(tag):
198n/a if "".join(e.itertext()) == value:
199n/a yield elem
200n/a break
201n/a return select
202n/a if signature == "-" or signature == "-()" or signature == "-()-":
203n/a # [index] or [last()] or [last()-index]
204n/a if signature == "-":
205n/a # [index]
206n/a index = int(predicate[0]) - 1
207n/a if index < 0:
208n/a raise SyntaxError("XPath position >= 1 expected")
209n/a else:
210n/a if predicate[0] != "last":
211n/a raise SyntaxError("unsupported function")
212n/a if signature == "-()-":
213n/a try:
214n/a index = int(predicate[2]) - 1
215n/a except ValueError:
216n/a raise SyntaxError("unsupported expression")
217n/a if index > -2:
218n/a raise SyntaxError("XPath offset from last() must be negative")
219n/a else:
220n/a index = -1
221n/a def select(context, result):
222n/a parent_map = get_parent_map(context)
223n/a for elem in result:
224n/a try:
225n/a parent = parent_map[elem]
226n/a # FIXME: what if the selector is "*" ?
227n/a elems = list(parent.findall(elem.tag))
228n/a if elems[index] is elem:
229n/a yield elem
230n/a except (IndexError, KeyError):
231n/a pass
232n/a return select
233n/a raise SyntaxError("invalid predicate")
234n/a
235n/aops = {
236n/a "": prepare_child,
237n/a "*": prepare_star,
238n/a ".": prepare_self,
239n/a "..": prepare_parent,
240n/a "//": prepare_descendant,
241n/a "[": prepare_predicate,
242n/a }
243n/a
244n/a_cache = {}
245n/a
246n/aclass _SelectorContext:
247n/a parent_map = None
248n/a def __init__(self, root):
249n/a self.root = root
250n/a
251n/a# --------------------------------------------------------------------
252n/a
253n/a##
254n/a# Generate all matching objects.
255n/a
256n/adef iterfind(elem, path, namespaces=None):
257n/a # compile selector pattern
258n/a cache_key = (path, None if namespaces is None
259n/a else tuple(sorted(namespaces.items())))
260n/a if path[-1:] == "/":
261n/a path = path + "*" # implicit all (FIXME: keep this?)
262n/a try:
263n/a selector = _cache[cache_key]
264n/a except KeyError:
265n/a if len(_cache) > 100:
266n/a _cache.clear()
267n/a if path[:1] == "/":
268n/a raise SyntaxError("cannot use absolute path on element")
269n/a next = iter(xpath_tokenizer(path, namespaces)).__next__
270n/a try:
271n/a token = next()
272n/a except StopIteration:
273n/a return
274n/a selector = []
275n/a while 1:
276n/a try:
277n/a selector.append(ops[token[0]](next, token))
278n/a except StopIteration:
279n/a raise SyntaxError("invalid path")
280n/a try:
281n/a token = next()
282n/a if token[0] == "/":
283n/a token = next()
284n/a except StopIteration:
285n/a break
286n/a _cache[cache_key] = selector
287n/a # execute selector pattern
288n/a result = [elem]
289n/a context = _SelectorContext(elem)
290n/a for select in selector:
291n/a result = select(context, result)
292n/a return result
293n/a
294n/a##
295n/a# Find first matching object.
296n/a
297n/adef find(elem, path, namespaces=None):
298n/a return next(iterfind(elem, path, namespaces), None)
299n/a
300n/a##
301n/a# Find all matching objects.
302n/a
303n/adef findall(elem, path, namespaces=None):
304n/a return list(iterfind(elem, path, namespaces))
305n/a
306n/a##
307n/a# Find text for first matching object.
308n/a
309n/adef findtext(elem, path, default=None, namespaces=None):
310n/a try:
311n/a elem = next(iterfind(elem, path, namespaces))
312n/a return elem.text or ""
313n/a except StopIteration:
314n/a return default