Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
9 changes: 6 additions & 3 deletions Doc/library/xml.etree.elementtree.rst
Original file line number Diff line number Diff line change
Expand Up @@ -755,15 +755,17 @@ Element Objects
Finds the first subelement matching *match*. *match* may be a tag name
or a :ref:`path <elementtree-xpath>`. Returns an element instance
or ``None``. *namespaces* is an optional mapping from namespace prefix
to full name.
to full name. Pass ``None`` as prefix to move all unprefixed tag names
in the expression into the given namespace.


.. method:: findall(match, namespaces=None)

Finds all matching subelements, by tag name or
:ref:`path <elementtree-xpath>`. Returns a list containing all matching
elements in document order. *namespaces* is an optional mapping from
namespace prefix to full name.
namespace prefix to full name. Pass ``None`` as prefix to move all
unprefixed tag names in the expression into the given namespace.


.. method:: findtext(match, default=None, namespaces=None)
Expand All @@ -773,7 +775,8 @@ Element Objects
of the first matching element, or *default* if no element was found.
Note that if the matching element has no text content an empty string
is returned. *namespaces* is an optional mapping from namespace prefix
to full name.
to full name. Pass ``None`` as prefix to move all unprefixed tag names
in the expression into the given namespace.


.. method:: getchildren()
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_xml_etree.py
Original file line number Diff line number Diff line change
Expand Up @@ -2340,6 +2340,12 @@ def test_findall_different_nsmaps(self):
nsmap = {'xx': 'Y'}
self.assertEqual(len(root.findall(".//xx:b", namespaces=nsmap)), 1)
self.assertEqual(len(root.findall(".//b", namespaces=nsmap)), 2)
nsmap = {'xx': 'X', None: 'Y'}
self.assertEqual(len(root.findall(".//xx:b", namespaces=nsmap)), 2)
self.assertEqual(len(root.findall(".//b", namespaces=nsmap)), 1)
nsmap = {'xx': 'X', '': 'Y'}
with self.assertRaisesRegex(ValueError, 'namespace prefix'):
root.findall(".//xx:b", namespaces=nsmap)

def test_bad_find(self):
e = ET.XML(SAMPLE_XML)
Expand Down
33 changes: 24 additions & 9 deletions Lib/xml/etree/ElementPath.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,22 @@
)

def xpath_tokenizer(pattern, namespaces=None):
default_namespace = namespaces.get(None) if namespaces else None
for token in xpath_tokenizer_re.findall(pattern):
tag = token[1]
if tag and tag[0] != "{" and ":" in tag:
try:
if tag and tag[0] != "{":
if ":" in tag:
prefix, uri = tag.split(":", 1)
if not namespaces:
raise KeyError
yield token[0], "{%s}%s" % (namespaces[prefix], uri)
except KeyError:
raise SyntaxError("prefix %r not found in prefix map" % prefix) from None
try:
if not namespaces:
raise KeyError
yield token[0], "{%s}%s" % (namespaces[prefix], uri)
except KeyError:
raise SyntaxError("prefix %r not found in prefix map" % prefix) from None
elif default_namespace:
yield token[0], "{%s}%s" % (default_namespace, tag)
else:
yield token
else:
yield token

Expand Down Expand Up @@ -264,10 +270,19 @@ def __init__(self, root):

def iterfind(elem, path, namespaces=None):
# compile selector pattern
cache_key = (path, None if namespaces is None
else tuple(sorted(namespaces.items())))
if path[-1:] == "/":
path = path + "*" # implicit all (FIXME: keep this?)

cache_key = (path,)
if namespaces:
if '' in namespaces:
raise ValueError("empty namespace prefix must be passed as None, not the empty string")
if None in namespaces:
cache_key += (namespaces[None],) + tuple(sorted(
item for item in namespaces.items() if item[0] is not None))
else:
cache_key += tuple(sorted(namespaces.items()))

try:
selector = _cache[cache_key]
except KeyError:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Path expressions in xml.etree.ElementTree can now avoid explicit namespace
prefixes for tags (or the "{namespace}tag" notation) by passing a default
namespace with a 'None' prefix.