Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
b132637: Fix positional xpath predicates when default namespace provided
  • Loading branch information
danifus committed Apr 23, 2025
commit 1c9f6860049d608b42f1918325d91e7ca9a8433d
21 changes: 18 additions & 3 deletions Lib/xml/etree/ElementPath.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
xpath_tokenizer_re = re.compile(
r"("
r"'[^']*'|\"[^\"]*\"|"
r"last\(\)|" # Pick out the only xpath function currently supported
r"::|"
r"//?|"
r"\.\.|"
Expand All @@ -71,6 +72,9 @@
r"\s+"
)

# Find integers, possibly preceded by - or +
int_re = re.compile(r"[\+\-]?\d+$")

def xpath_tokenizer(pattern, namespaces=None):
default_namespace = namespaces.get('') if namespaces else None
parsing_attribute = False
Expand All @@ -85,11 +89,22 @@ def xpath_tokenizer(pattern, namespaces=None):
yield ttype, "{%s}%s" % (namespaces[prefix], uri)
except KeyError:
raise SyntaxError("prefix %r not found in prefix map" % prefix) from None
elif default_namespace and not parsing_attribute:
# We don't preprend the default_namespace when:
# - the tag is an attribute as the xml spec says default namespaces
# don't apply to attributes
# - when the tag is a number, possibly preceded by - or +, as these
# are not valid characters to start a tag with and are probably
# used as positional predicates.
elif default_namespace and not (parsing_attribute or int_re.match(tag)):
yield ttype, "{%s}%s" % (default_namespace, tag)
else:
yield token
parsing_attribute = False
elif ttype == 'last()':
# Break the found 'last()' part into the separate 'tag' and 'ttype'
# separate returned values expected from this generator
yield ('', 'last')
yield ('()', '')
else:
yield token
parsing_attribute = ttype == '@'
Expand Down Expand Up @@ -266,7 +281,7 @@ def select_negated(context, result):
if (attr_value := elem.get(key)) is not None and attr_value != value:
yield elem
return select_negated if '!=' in signature else select
if signature == "-" and not re.match(r"\-?\d+$", predicate[0]):
if signature == "-" and not int_re.match(predicate[0]):
# [tag]
tag = predicate[0]
def select(context, result):
Expand All @@ -276,7 +291,7 @@ def select(context, result):
return select
if signature == ".='" or signature == ".!='" or (
(signature == "-='" or signature == "-!='")
and not re.match(r"\-?\d+$", predicate[0])):
and not int_re.match(predicate[0])):
# [.='value'] or [tag='value'] or [.!='value'] or [tag!='value']
tag = predicate[0]
value = predicate[-1]
Expand Down