xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/xml/etree/ElementPath.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker#
2*cda5da8dSAndroid Build Coastguard Worker# ElementTree
3*cda5da8dSAndroid Build Coastguard Worker# $Id: ElementPath.py 3375 2008-02-13 08:05:08Z fredrik $
4*cda5da8dSAndroid Build Coastguard Worker#
5*cda5da8dSAndroid Build Coastguard Worker# limited xpath support for element trees
6*cda5da8dSAndroid Build Coastguard Worker#
7*cda5da8dSAndroid Build Coastguard Worker# history:
8*cda5da8dSAndroid Build Coastguard Worker# 2003-05-23 fl   created
9*cda5da8dSAndroid Build Coastguard Worker# 2003-05-28 fl   added support for // etc
10*cda5da8dSAndroid Build Coastguard Worker# 2003-08-27 fl   fixed parsing of periods in element names
11*cda5da8dSAndroid Build Coastguard Worker# 2007-09-10 fl   new selection engine
12*cda5da8dSAndroid Build Coastguard Worker# 2007-09-12 fl   fixed parent selector
13*cda5da8dSAndroid Build Coastguard Worker# 2007-09-13 fl   added iterfind; changed findall to return a list
14*cda5da8dSAndroid Build Coastguard Worker# 2007-11-30 fl   added namespaces support
15*cda5da8dSAndroid Build Coastguard Worker# 2009-10-30 fl   added child element value filter
16*cda5da8dSAndroid Build Coastguard Worker#
17*cda5da8dSAndroid Build Coastguard Worker# Copyright (c) 2003-2009 by Fredrik Lundh.  All rights reserved.
18*cda5da8dSAndroid Build Coastguard Worker#
19*cda5da8dSAndroid Build Coastguard Worker# [email protected]
20*cda5da8dSAndroid Build Coastguard Worker# http://www.pythonware.com
21*cda5da8dSAndroid Build Coastguard Worker#
22*cda5da8dSAndroid Build Coastguard Worker# --------------------------------------------------------------------
23*cda5da8dSAndroid Build Coastguard Worker# The ElementTree toolkit is
24*cda5da8dSAndroid Build Coastguard Worker#
25*cda5da8dSAndroid Build Coastguard Worker# Copyright (c) 1999-2009 by Fredrik Lundh
26*cda5da8dSAndroid Build Coastguard Worker#
27*cda5da8dSAndroid Build Coastguard Worker# By obtaining, using, and/or copying this software and/or its
28*cda5da8dSAndroid Build Coastguard Worker# associated documentation, you agree that you have read, understood,
29*cda5da8dSAndroid Build Coastguard Worker# and will comply with the following terms and conditions:
30*cda5da8dSAndroid Build Coastguard Worker#
31*cda5da8dSAndroid Build Coastguard Worker# Permission to use, copy, modify, and distribute this software and
32*cda5da8dSAndroid Build Coastguard Worker# its associated documentation for any purpose and without fee is
33*cda5da8dSAndroid Build Coastguard Worker# hereby granted, provided that the above copyright notice appears in
34*cda5da8dSAndroid Build Coastguard Worker# all copies, and that both that copyright notice and this permission
35*cda5da8dSAndroid Build Coastguard Worker# notice appear in supporting documentation, and that the name of
36*cda5da8dSAndroid Build Coastguard Worker# Secret Labs AB or the author not be used in advertising or publicity
37*cda5da8dSAndroid Build Coastguard Worker# pertaining to distribution of the software without specific, written
38*cda5da8dSAndroid Build Coastguard Worker# prior permission.
39*cda5da8dSAndroid Build Coastguard Worker#
40*cda5da8dSAndroid Build Coastguard Worker# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
41*cda5da8dSAndroid Build Coastguard Worker# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
42*cda5da8dSAndroid Build Coastguard Worker# ABILITY AND FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
43*cda5da8dSAndroid Build Coastguard Worker# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
44*cda5da8dSAndroid Build Coastguard Worker# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
45*cda5da8dSAndroid Build Coastguard Worker# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
46*cda5da8dSAndroid Build Coastguard Worker# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
47*cda5da8dSAndroid Build Coastguard Worker# OF THIS SOFTWARE.
48*cda5da8dSAndroid Build Coastguard Worker# --------------------------------------------------------------------
49*cda5da8dSAndroid Build Coastguard Worker
50*cda5da8dSAndroid Build Coastguard Worker# Licensed to PSF under a Contributor Agreement.
51*cda5da8dSAndroid Build Coastguard Worker# See https://www.python.org/psf/license for licensing details.
52*cda5da8dSAndroid Build Coastguard Worker
53*cda5da8dSAndroid Build Coastguard Worker##
54*cda5da8dSAndroid Build Coastguard Worker# Implementation module for XPath support.  There's usually no reason
55*cda5da8dSAndroid Build Coastguard Worker# to import this module directly; the <b>ElementTree</b> does this for
56*cda5da8dSAndroid Build Coastguard Worker# you, if needed.
57*cda5da8dSAndroid Build Coastguard Worker##
58*cda5da8dSAndroid Build Coastguard Worker
59*cda5da8dSAndroid Build Coastguard Workerimport re
60*cda5da8dSAndroid Build Coastguard Worker
61*cda5da8dSAndroid Build Coastguard Workerxpath_tokenizer_re = re.compile(
62*cda5da8dSAndroid Build Coastguard Worker    r"("
63*cda5da8dSAndroid Build Coastguard Worker    r"'[^']*'|\"[^\"]*\"|"
64*cda5da8dSAndroid Build Coastguard Worker    r"::|"
65*cda5da8dSAndroid Build Coastguard Worker    r"//?|"
66*cda5da8dSAndroid Build Coastguard Worker    r"\.\.|"
67*cda5da8dSAndroid Build Coastguard Worker    r"\(\)|"
68*cda5da8dSAndroid Build Coastguard Worker    r"!=|"
69*cda5da8dSAndroid Build Coastguard Worker    r"[/.*:\[\]\(\)@=])|"
70*cda5da8dSAndroid Build Coastguard Worker    r"((?:\{[^}]+\})?[^/\[\]\(\)@!=\s]+)|"
71*cda5da8dSAndroid Build Coastguard Worker    r"\s+"
72*cda5da8dSAndroid Build Coastguard Worker    )
73*cda5da8dSAndroid Build Coastguard Worker
74*cda5da8dSAndroid Build Coastguard Workerdef xpath_tokenizer(pattern, namespaces=None):
75*cda5da8dSAndroid Build Coastguard Worker    default_namespace = namespaces.get('') if namespaces else None
76*cda5da8dSAndroid Build Coastguard Worker    parsing_attribute = False
77*cda5da8dSAndroid Build Coastguard Worker    for token in xpath_tokenizer_re.findall(pattern):
78*cda5da8dSAndroid Build Coastguard Worker        ttype, tag = token
79*cda5da8dSAndroid Build Coastguard Worker        if tag and tag[0] != "{":
80*cda5da8dSAndroid Build Coastguard Worker            if ":" in tag:
81*cda5da8dSAndroid Build Coastguard Worker                prefix, uri = tag.split(":", 1)
82*cda5da8dSAndroid Build Coastguard Worker                try:
83*cda5da8dSAndroid Build Coastguard Worker                    if not namespaces:
84*cda5da8dSAndroid Build Coastguard Worker                        raise KeyError
85*cda5da8dSAndroid Build Coastguard Worker                    yield ttype, "{%s}%s" % (namespaces[prefix], uri)
86*cda5da8dSAndroid Build Coastguard Worker                except KeyError:
87*cda5da8dSAndroid Build Coastguard Worker                    raise SyntaxError("prefix %r not found in prefix map" % prefix) from None
88*cda5da8dSAndroid Build Coastguard Worker            elif default_namespace and not parsing_attribute:
89*cda5da8dSAndroid Build Coastguard Worker                yield ttype, "{%s}%s" % (default_namespace, tag)
90*cda5da8dSAndroid Build Coastguard Worker            else:
91*cda5da8dSAndroid Build Coastguard Worker                yield token
92*cda5da8dSAndroid Build Coastguard Worker            parsing_attribute = False
93*cda5da8dSAndroid Build Coastguard Worker        else:
94*cda5da8dSAndroid Build Coastguard Worker            yield token
95*cda5da8dSAndroid Build Coastguard Worker            parsing_attribute = ttype == '@'
96*cda5da8dSAndroid Build Coastguard Worker
97*cda5da8dSAndroid Build Coastguard Worker
98*cda5da8dSAndroid Build Coastguard Workerdef get_parent_map(context):
99*cda5da8dSAndroid Build Coastguard Worker    parent_map = context.parent_map
100*cda5da8dSAndroid Build Coastguard Worker    if parent_map is None:
101*cda5da8dSAndroid Build Coastguard Worker        context.parent_map = parent_map = {}
102*cda5da8dSAndroid Build Coastguard Worker        for p in context.root.iter():
103*cda5da8dSAndroid Build Coastguard Worker            for e in p:
104*cda5da8dSAndroid Build Coastguard Worker                parent_map[e] = p
105*cda5da8dSAndroid Build Coastguard Worker    return parent_map
106*cda5da8dSAndroid Build Coastguard Worker
107*cda5da8dSAndroid Build Coastguard Worker
108*cda5da8dSAndroid Build Coastguard Workerdef _is_wildcard_tag(tag):
109*cda5da8dSAndroid Build Coastguard Worker    return tag[:3] == '{*}' or tag[-2:] == '}*'
110*cda5da8dSAndroid Build Coastguard Worker
111*cda5da8dSAndroid Build Coastguard Worker
112*cda5da8dSAndroid Build Coastguard Workerdef _prepare_tag(tag):
113*cda5da8dSAndroid Build Coastguard Worker    _isinstance, _str = isinstance, str
114*cda5da8dSAndroid Build Coastguard Worker    if tag == '{*}*':
115*cda5da8dSAndroid Build Coastguard Worker        # Same as '*', but no comments or processing instructions.
116*cda5da8dSAndroid Build Coastguard Worker        # It can be a surprise that '*' includes those, but there is no
117*cda5da8dSAndroid Build Coastguard Worker        # justification for '{*}*' doing the same.
118*cda5da8dSAndroid Build Coastguard Worker        def select(context, result):
119*cda5da8dSAndroid Build Coastguard Worker            for elem in result:
120*cda5da8dSAndroid Build Coastguard Worker                if _isinstance(elem.tag, _str):
121*cda5da8dSAndroid Build Coastguard Worker                    yield elem
122*cda5da8dSAndroid Build Coastguard Worker    elif tag == '{}*':
123*cda5da8dSAndroid Build Coastguard Worker        # Any tag that is not in a namespace.
124*cda5da8dSAndroid Build Coastguard Worker        def select(context, result):
125*cda5da8dSAndroid Build Coastguard Worker            for elem in result:
126*cda5da8dSAndroid Build Coastguard Worker                el_tag = elem.tag
127*cda5da8dSAndroid Build Coastguard Worker                if _isinstance(el_tag, _str) and el_tag[0] != '{':
128*cda5da8dSAndroid Build Coastguard Worker                    yield elem
129*cda5da8dSAndroid Build Coastguard Worker    elif tag[:3] == '{*}':
130*cda5da8dSAndroid Build Coastguard Worker        # The tag in any (or no) namespace.
131*cda5da8dSAndroid Build Coastguard Worker        suffix = tag[2:]  # '}name'
132*cda5da8dSAndroid Build Coastguard Worker        no_ns = slice(-len(suffix), None)
133*cda5da8dSAndroid Build Coastguard Worker        tag = tag[3:]
134*cda5da8dSAndroid Build Coastguard Worker        def select(context, result):
135*cda5da8dSAndroid Build Coastguard Worker            for elem in result:
136*cda5da8dSAndroid Build Coastguard Worker                el_tag = elem.tag
137*cda5da8dSAndroid Build Coastguard Worker                if el_tag == tag or _isinstance(el_tag, _str) and el_tag[no_ns] == suffix:
138*cda5da8dSAndroid Build Coastguard Worker                    yield elem
139*cda5da8dSAndroid Build Coastguard Worker    elif tag[-2:] == '}*':
140*cda5da8dSAndroid Build Coastguard Worker        # Any tag in the given namespace.
141*cda5da8dSAndroid Build Coastguard Worker        ns = tag[:-1]
142*cda5da8dSAndroid Build Coastguard Worker        ns_only = slice(None, len(ns))
143*cda5da8dSAndroid Build Coastguard Worker        def select(context, result):
144*cda5da8dSAndroid Build Coastguard Worker            for elem in result:
145*cda5da8dSAndroid Build Coastguard Worker                el_tag = elem.tag
146*cda5da8dSAndroid Build Coastguard Worker                if _isinstance(el_tag, _str) and el_tag[ns_only] == ns:
147*cda5da8dSAndroid Build Coastguard Worker                    yield elem
148*cda5da8dSAndroid Build Coastguard Worker    else:
149*cda5da8dSAndroid Build Coastguard Worker        raise RuntimeError(f"internal parser error, got {tag}")
150*cda5da8dSAndroid Build Coastguard Worker    return select
151*cda5da8dSAndroid Build Coastguard Worker
152*cda5da8dSAndroid Build Coastguard Worker
153*cda5da8dSAndroid Build Coastguard Workerdef prepare_child(next, token):
154*cda5da8dSAndroid Build Coastguard Worker    tag = token[1]
155*cda5da8dSAndroid Build Coastguard Worker    if _is_wildcard_tag(tag):
156*cda5da8dSAndroid Build Coastguard Worker        select_tag = _prepare_tag(tag)
157*cda5da8dSAndroid Build Coastguard Worker        def select(context, result):
158*cda5da8dSAndroid Build Coastguard Worker            def select_child(result):
159*cda5da8dSAndroid Build Coastguard Worker                for elem in result:
160*cda5da8dSAndroid Build Coastguard Worker                    yield from elem
161*cda5da8dSAndroid Build Coastguard Worker            return select_tag(context, select_child(result))
162*cda5da8dSAndroid Build Coastguard Worker    else:
163*cda5da8dSAndroid Build Coastguard Worker        if tag[:2] == '{}':
164*cda5da8dSAndroid Build Coastguard Worker            tag = tag[2:]  # '{}tag' == 'tag'
165*cda5da8dSAndroid Build Coastguard Worker        def select(context, result):
166*cda5da8dSAndroid Build Coastguard Worker            for elem in result:
167*cda5da8dSAndroid Build Coastguard Worker                for e in elem:
168*cda5da8dSAndroid Build Coastguard Worker                    if e.tag == tag:
169*cda5da8dSAndroid Build Coastguard Worker                        yield e
170*cda5da8dSAndroid Build Coastguard Worker    return select
171*cda5da8dSAndroid Build Coastguard Worker
172*cda5da8dSAndroid Build Coastguard Workerdef prepare_star(next, token):
173*cda5da8dSAndroid Build Coastguard Worker    def select(context, result):
174*cda5da8dSAndroid Build Coastguard Worker        for elem in result:
175*cda5da8dSAndroid Build Coastguard Worker            yield from elem
176*cda5da8dSAndroid Build Coastguard Worker    return select
177*cda5da8dSAndroid Build Coastguard Worker
178*cda5da8dSAndroid Build Coastguard Workerdef prepare_self(next, token):
179*cda5da8dSAndroid Build Coastguard Worker    def select(context, result):
180*cda5da8dSAndroid Build Coastguard Worker        yield from result
181*cda5da8dSAndroid Build Coastguard Worker    return select
182*cda5da8dSAndroid Build Coastguard Worker
183*cda5da8dSAndroid Build Coastguard Workerdef prepare_descendant(next, token):
184*cda5da8dSAndroid Build Coastguard Worker    try:
185*cda5da8dSAndroid Build Coastguard Worker        token = next()
186*cda5da8dSAndroid Build Coastguard Worker    except StopIteration:
187*cda5da8dSAndroid Build Coastguard Worker        return
188*cda5da8dSAndroid Build Coastguard Worker    if token[0] == "*":
189*cda5da8dSAndroid Build Coastguard Worker        tag = "*"
190*cda5da8dSAndroid Build Coastguard Worker    elif not token[0]:
191*cda5da8dSAndroid Build Coastguard Worker        tag = token[1]
192*cda5da8dSAndroid Build Coastguard Worker    else:
193*cda5da8dSAndroid Build Coastguard Worker        raise SyntaxError("invalid descendant")
194*cda5da8dSAndroid Build Coastguard Worker
195*cda5da8dSAndroid Build Coastguard Worker    if _is_wildcard_tag(tag):
196*cda5da8dSAndroid Build Coastguard Worker        select_tag = _prepare_tag(tag)
197*cda5da8dSAndroid Build Coastguard Worker        def select(context, result):
198*cda5da8dSAndroid Build Coastguard Worker            def select_child(result):
199*cda5da8dSAndroid Build Coastguard Worker                for elem in result:
200*cda5da8dSAndroid Build Coastguard Worker                    for e in elem.iter():
201*cda5da8dSAndroid Build Coastguard Worker                        if e is not elem:
202*cda5da8dSAndroid Build Coastguard Worker                            yield e
203*cda5da8dSAndroid Build Coastguard Worker            return select_tag(context, select_child(result))
204*cda5da8dSAndroid Build Coastguard Worker    else:
205*cda5da8dSAndroid Build Coastguard Worker        if tag[:2] == '{}':
206*cda5da8dSAndroid Build Coastguard Worker            tag = tag[2:]  # '{}tag' == 'tag'
207*cda5da8dSAndroid Build Coastguard Worker        def select(context, result):
208*cda5da8dSAndroid Build Coastguard Worker            for elem in result:
209*cda5da8dSAndroid Build Coastguard Worker                for e in elem.iter(tag):
210*cda5da8dSAndroid Build Coastguard Worker                    if e is not elem:
211*cda5da8dSAndroid Build Coastguard Worker                        yield e
212*cda5da8dSAndroid Build Coastguard Worker    return select
213*cda5da8dSAndroid Build Coastguard Worker
214*cda5da8dSAndroid Build Coastguard Workerdef prepare_parent(next, token):
215*cda5da8dSAndroid Build Coastguard Worker    def select(context, result):
216*cda5da8dSAndroid Build Coastguard Worker        # FIXME: raise error if .. is applied at toplevel?
217*cda5da8dSAndroid Build Coastguard Worker        parent_map = get_parent_map(context)
218*cda5da8dSAndroid Build Coastguard Worker        result_map = {}
219*cda5da8dSAndroid Build Coastguard Worker        for elem in result:
220*cda5da8dSAndroid Build Coastguard Worker            if elem in parent_map:
221*cda5da8dSAndroid Build Coastguard Worker                parent = parent_map[elem]
222*cda5da8dSAndroid Build Coastguard Worker                if parent not in result_map:
223*cda5da8dSAndroid Build Coastguard Worker                    result_map[parent] = None
224*cda5da8dSAndroid Build Coastguard Worker                    yield parent
225*cda5da8dSAndroid Build Coastguard Worker    return select
226*cda5da8dSAndroid Build Coastguard Worker
227*cda5da8dSAndroid Build Coastguard Workerdef prepare_predicate(next, token):
228*cda5da8dSAndroid Build Coastguard Worker    # FIXME: replace with real parser!!! refs:
229*cda5da8dSAndroid Build Coastguard Worker    # http://javascript.crockford.com/tdop/tdop.html
230*cda5da8dSAndroid Build Coastguard Worker    signature = []
231*cda5da8dSAndroid Build Coastguard Worker    predicate = []
232*cda5da8dSAndroid Build Coastguard Worker    while 1:
233*cda5da8dSAndroid Build Coastguard Worker        try:
234*cda5da8dSAndroid Build Coastguard Worker            token = next()
235*cda5da8dSAndroid Build Coastguard Worker        except StopIteration:
236*cda5da8dSAndroid Build Coastguard Worker            return
237*cda5da8dSAndroid Build Coastguard Worker        if token[0] == "]":
238*cda5da8dSAndroid Build Coastguard Worker            break
239*cda5da8dSAndroid Build Coastguard Worker        if token == ('', ''):
240*cda5da8dSAndroid Build Coastguard Worker            # ignore whitespace
241*cda5da8dSAndroid Build Coastguard Worker            continue
242*cda5da8dSAndroid Build Coastguard Worker        if token[0] and token[0][:1] in "'\"":
243*cda5da8dSAndroid Build Coastguard Worker            token = "'", token[0][1:-1]
244*cda5da8dSAndroid Build Coastguard Worker        signature.append(token[0] or "-")
245*cda5da8dSAndroid Build Coastguard Worker        predicate.append(token[1])
246*cda5da8dSAndroid Build Coastguard Worker    signature = "".join(signature)
247*cda5da8dSAndroid Build Coastguard Worker    # use signature to determine predicate type
248*cda5da8dSAndroid Build Coastguard Worker    if signature == "@-":
249*cda5da8dSAndroid Build Coastguard Worker        # [@attribute] predicate
250*cda5da8dSAndroid Build Coastguard Worker        key = predicate[1]
251*cda5da8dSAndroid Build Coastguard Worker        def select(context, result):
252*cda5da8dSAndroid Build Coastguard Worker            for elem in result:
253*cda5da8dSAndroid Build Coastguard Worker                if elem.get(key) is not None:
254*cda5da8dSAndroid Build Coastguard Worker                    yield elem
255*cda5da8dSAndroid Build Coastguard Worker        return select
256*cda5da8dSAndroid Build Coastguard Worker    if signature == "@-='" or signature == "@-!='":
257*cda5da8dSAndroid Build Coastguard Worker        # [@attribute='value'] or [@attribute!='value']
258*cda5da8dSAndroid Build Coastguard Worker        key = predicate[1]
259*cda5da8dSAndroid Build Coastguard Worker        value = predicate[-1]
260*cda5da8dSAndroid Build Coastguard Worker        def select(context, result):
261*cda5da8dSAndroid Build Coastguard Worker            for elem in result:
262*cda5da8dSAndroid Build Coastguard Worker                if elem.get(key) == value:
263*cda5da8dSAndroid Build Coastguard Worker                    yield elem
264*cda5da8dSAndroid Build Coastguard Worker        def select_negated(context, result):
265*cda5da8dSAndroid Build Coastguard Worker            for elem in result:
266*cda5da8dSAndroid Build Coastguard Worker                if (attr_value := elem.get(key)) is not None and attr_value != value:
267*cda5da8dSAndroid Build Coastguard Worker                    yield elem
268*cda5da8dSAndroid Build Coastguard Worker        return select_negated if '!=' in signature else select
269*cda5da8dSAndroid Build Coastguard Worker    if signature == "-" and not re.match(r"\-?\d+$", predicate[0]):
270*cda5da8dSAndroid Build Coastguard Worker        # [tag]
271*cda5da8dSAndroid Build Coastguard Worker        tag = predicate[0]
272*cda5da8dSAndroid Build Coastguard Worker        def select(context, result):
273*cda5da8dSAndroid Build Coastguard Worker            for elem in result:
274*cda5da8dSAndroid Build Coastguard Worker                if elem.find(tag) is not None:
275*cda5da8dSAndroid Build Coastguard Worker                    yield elem
276*cda5da8dSAndroid Build Coastguard Worker        return select
277*cda5da8dSAndroid Build Coastguard Worker    if signature == ".='" or signature == ".!='" or (
278*cda5da8dSAndroid Build Coastguard Worker            (signature == "-='" or signature == "-!='")
279*cda5da8dSAndroid Build Coastguard Worker            and not re.match(r"\-?\d+$", predicate[0])):
280*cda5da8dSAndroid Build Coastguard Worker        # [.='value'] or [tag='value'] or [.!='value'] or [tag!='value']
281*cda5da8dSAndroid Build Coastguard Worker        tag = predicate[0]
282*cda5da8dSAndroid Build Coastguard Worker        value = predicate[-1]
283*cda5da8dSAndroid Build Coastguard Worker        if tag:
284*cda5da8dSAndroid Build Coastguard Worker            def select(context, result):
285*cda5da8dSAndroid Build Coastguard Worker                for elem in result:
286*cda5da8dSAndroid Build Coastguard Worker                    for e in elem.findall(tag):
287*cda5da8dSAndroid Build Coastguard Worker                        if "".join(e.itertext()) == value:
288*cda5da8dSAndroid Build Coastguard Worker                            yield elem
289*cda5da8dSAndroid Build Coastguard Worker                            break
290*cda5da8dSAndroid Build Coastguard Worker            def select_negated(context, result):
291*cda5da8dSAndroid Build Coastguard Worker                for elem in result:
292*cda5da8dSAndroid Build Coastguard Worker                    for e in elem.iterfind(tag):
293*cda5da8dSAndroid Build Coastguard Worker                        if "".join(e.itertext()) != value:
294*cda5da8dSAndroid Build Coastguard Worker                            yield elem
295*cda5da8dSAndroid Build Coastguard Worker                            break
296*cda5da8dSAndroid Build Coastguard Worker        else:
297*cda5da8dSAndroid Build Coastguard Worker            def select(context, result):
298*cda5da8dSAndroid Build Coastguard Worker                for elem in result:
299*cda5da8dSAndroid Build Coastguard Worker                    if "".join(elem.itertext()) == value:
300*cda5da8dSAndroid Build Coastguard Worker                        yield elem
301*cda5da8dSAndroid Build Coastguard Worker            def select_negated(context, result):
302*cda5da8dSAndroid Build Coastguard Worker                for elem in result:
303*cda5da8dSAndroid Build Coastguard Worker                    if "".join(elem.itertext()) != value:
304*cda5da8dSAndroid Build Coastguard Worker                        yield elem
305*cda5da8dSAndroid Build Coastguard Worker        return select_negated if '!=' in signature else select
306*cda5da8dSAndroid Build Coastguard Worker    if signature == "-" or signature == "-()" or signature == "-()-":
307*cda5da8dSAndroid Build Coastguard Worker        # [index] or [last()] or [last()-index]
308*cda5da8dSAndroid Build Coastguard Worker        if signature == "-":
309*cda5da8dSAndroid Build Coastguard Worker            # [index]
310*cda5da8dSAndroid Build Coastguard Worker            index = int(predicate[0]) - 1
311*cda5da8dSAndroid Build Coastguard Worker            if index < 0:
312*cda5da8dSAndroid Build Coastguard Worker                raise SyntaxError("XPath position >= 1 expected")
313*cda5da8dSAndroid Build Coastguard Worker        else:
314*cda5da8dSAndroid Build Coastguard Worker            if predicate[0] != "last":
315*cda5da8dSAndroid Build Coastguard Worker                raise SyntaxError("unsupported function")
316*cda5da8dSAndroid Build Coastguard Worker            if signature == "-()-":
317*cda5da8dSAndroid Build Coastguard Worker                try:
318*cda5da8dSAndroid Build Coastguard Worker                    index = int(predicate[2]) - 1
319*cda5da8dSAndroid Build Coastguard Worker                except ValueError:
320*cda5da8dSAndroid Build Coastguard Worker                    raise SyntaxError("unsupported expression")
321*cda5da8dSAndroid Build Coastguard Worker                if index > -2:
322*cda5da8dSAndroid Build Coastguard Worker                    raise SyntaxError("XPath offset from last() must be negative")
323*cda5da8dSAndroid Build Coastguard Worker            else:
324*cda5da8dSAndroid Build Coastguard Worker                index = -1
325*cda5da8dSAndroid Build Coastguard Worker        def select(context, result):
326*cda5da8dSAndroid Build Coastguard Worker            parent_map = get_parent_map(context)
327*cda5da8dSAndroid Build Coastguard Worker            for elem in result:
328*cda5da8dSAndroid Build Coastguard Worker                try:
329*cda5da8dSAndroid Build Coastguard Worker                    parent = parent_map[elem]
330*cda5da8dSAndroid Build Coastguard Worker                    # FIXME: what if the selector is "*" ?
331*cda5da8dSAndroid Build Coastguard Worker                    elems = list(parent.findall(elem.tag))
332*cda5da8dSAndroid Build Coastguard Worker                    if elems[index] is elem:
333*cda5da8dSAndroid Build Coastguard Worker                        yield elem
334*cda5da8dSAndroid Build Coastguard Worker                except (IndexError, KeyError):
335*cda5da8dSAndroid Build Coastguard Worker                    pass
336*cda5da8dSAndroid Build Coastguard Worker        return select
337*cda5da8dSAndroid Build Coastguard Worker    raise SyntaxError("invalid predicate")
338*cda5da8dSAndroid Build Coastguard Worker
339*cda5da8dSAndroid Build Coastguard Workerops = {
340*cda5da8dSAndroid Build Coastguard Worker    "": prepare_child,
341*cda5da8dSAndroid Build Coastguard Worker    "*": prepare_star,
342*cda5da8dSAndroid Build Coastguard Worker    ".": prepare_self,
343*cda5da8dSAndroid Build Coastguard Worker    "..": prepare_parent,
344*cda5da8dSAndroid Build Coastguard Worker    "//": prepare_descendant,
345*cda5da8dSAndroid Build Coastguard Worker    "[": prepare_predicate,
346*cda5da8dSAndroid Build Coastguard Worker    }
347*cda5da8dSAndroid Build Coastguard Worker
348*cda5da8dSAndroid Build Coastguard Worker_cache = {}
349*cda5da8dSAndroid Build Coastguard Worker
350*cda5da8dSAndroid Build Coastguard Workerclass _SelectorContext:
351*cda5da8dSAndroid Build Coastguard Worker    parent_map = None
352*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, root):
353*cda5da8dSAndroid Build Coastguard Worker        self.root = root
354*cda5da8dSAndroid Build Coastguard Worker
355*cda5da8dSAndroid Build Coastguard Worker# --------------------------------------------------------------------
356*cda5da8dSAndroid Build Coastguard Worker
357*cda5da8dSAndroid Build Coastguard Worker##
358*cda5da8dSAndroid Build Coastguard Worker# Generate all matching objects.
359*cda5da8dSAndroid Build Coastguard Worker
360*cda5da8dSAndroid Build Coastguard Workerdef iterfind(elem, path, namespaces=None):
361*cda5da8dSAndroid Build Coastguard Worker    # compile selector pattern
362*cda5da8dSAndroid Build Coastguard Worker    if path[-1:] == "/":
363*cda5da8dSAndroid Build Coastguard Worker        path = path + "*" # implicit all (FIXME: keep this?)
364*cda5da8dSAndroid Build Coastguard Worker
365*cda5da8dSAndroid Build Coastguard Worker    cache_key = (path,)
366*cda5da8dSAndroid Build Coastguard Worker    if namespaces:
367*cda5da8dSAndroid Build Coastguard Worker        cache_key += tuple(sorted(namespaces.items()))
368*cda5da8dSAndroid Build Coastguard Worker
369*cda5da8dSAndroid Build Coastguard Worker    try:
370*cda5da8dSAndroid Build Coastguard Worker        selector = _cache[cache_key]
371*cda5da8dSAndroid Build Coastguard Worker    except KeyError:
372*cda5da8dSAndroid Build Coastguard Worker        if len(_cache) > 100:
373*cda5da8dSAndroid Build Coastguard Worker            _cache.clear()
374*cda5da8dSAndroid Build Coastguard Worker        if path[:1] == "/":
375*cda5da8dSAndroid Build Coastguard Worker            raise SyntaxError("cannot use absolute path on element")
376*cda5da8dSAndroid Build Coastguard Worker        next = iter(xpath_tokenizer(path, namespaces)).__next__
377*cda5da8dSAndroid Build Coastguard Worker        try:
378*cda5da8dSAndroid Build Coastguard Worker            token = next()
379*cda5da8dSAndroid Build Coastguard Worker        except StopIteration:
380*cda5da8dSAndroid Build Coastguard Worker            return
381*cda5da8dSAndroid Build Coastguard Worker        selector = []
382*cda5da8dSAndroid Build Coastguard Worker        while 1:
383*cda5da8dSAndroid Build Coastguard Worker            try:
384*cda5da8dSAndroid Build Coastguard Worker                selector.append(ops[token[0]](next, token))
385*cda5da8dSAndroid Build Coastguard Worker            except StopIteration:
386*cda5da8dSAndroid Build Coastguard Worker                raise SyntaxError("invalid path") from None
387*cda5da8dSAndroid Build Coastguard Worker            try:
388*cda5da8dSAndroid Build Coastguard Worker                token = next()
389*cda5da8dSAndroid Build Coastguard Worker                if token[0] == "/":
390*cda5da8dSAndroid Build Coastguard Worker                    token = next()
391*cda5da8dSAndroid Build Coastguard Worker            except StopIteration:
392*cda5da8dSAndroid Build Coastguard Worker                break
393*cda5da8dSAndroid Build Coastguard Worker        _cache[cache_key] = selector
394*cda5da8dSAndroid Build Coastguard Worker    # execute selector pattern
395*cda5da8dSAndroid Build Coastguard Worker    result = [elem]
396*cda5da8dSAndroid Build Coastguard Worker    context = _SelectorContext(elem)
397*cda5da8dSAndroid Build Coastguard Worker    for select in selector:
398*cda5da8dSAndroid Build Coastguard Worker        result = select(context, result)
399*cda5da8dSAndroid Build Coastguard Worker    return result
400*cda5da8dSAndroid Build Coastguard Worker
401*cda5da8dSAndroid Build Coastguard Worker##
402*cda5da8dSAndroid Build Coastguard Worker# Find first matching object.
403*cda5da8dSAndroid Build Coastguard Worker
404*cda5da8dSAndroid Build Coastguard Workerdef find(elem, path, namespaces=None):
405*cda5da8dSAndroid Build Coastguard Worker    return next(iterfind(elem, path, namespaces), None)
406*cda5da8dSAndroid Build Coastguard Worker
407*cda5da8dSAndroid Build Coastguard Worker##
408*cda5da8dSAndroid Build Coastguard Worker# Find all matching objects.
409*cda5da8dSAndroid Build Coastguard Worker
410*cda5da8dSAndroid Build Coastguard Workerdef findall(elem, path, namespaces=None):
411*cda5da8dSAndroid Build Coastguard Worker    return list(iterfind(elem, path, namespaces))
412*cda5da8dSAndroid Build Coastguard Worker
413*cda5da8dSAndroid Build Coastguard Worker##
414*cda5da8dSAndroid Build Coastguard Worker# Find text for first matching object.
415*cda5da8dSAndroid Build Coastguard Worker
416*cda5da8dSAndroid Build Coastguard Workerdef findtext(elem, path, default=None, namespaces=None):
417*cda5da8dSAndroid Build Coastguard Worker    try:
418*cda5da8dSAndroid Build Coastguard Worker        elem = next(iterfind(elem, path, namespaces))
419*cda5da8dSAndroid Build Coastguard Worker        if elem.text is None:
420*cda5da8dSAndroid Build Coastguard Worker            return ""
421*cda5da8dSAndroid Build Coastguard Worker        return elem.text
422*cda5da8dSAndroid Build Coastguard Worker    except StopIteration:
423*cda5da8dSAndroid Build Coastguard Worker        return default
424