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