bpo-30485: support a default prefix mapping in ElementPath by passing… · python/cpython@e9927e1 (original) (raw)

4 files changed

lines changed

Original file line number Diff line number Diff line change
@@ -764,15 +764,17 @@ Element Objects
764 764 Finds the first subelement matching *match*. *match* may be a tag name
765 765 or a :ref:`path <elementtree-xpath>`. Returns an element instance
766 766 or ``None``. *namespaces* is an optional mapping from namespace prefix
767 - to full name.
767 + to full name. Pass ``None`` as prefix to move all unprefixed tag names
768 + in the expression into the given namespace.
768 769
769 770
770 771 .. method:: findall(match, namespaces=None)
771 772
772 773 Finds all matching subelements, by tag name or
773 774 :ref:`path <elementtree-xpath>`. Returns a list containing all matching
774 775 elements in document order. *namespaces* is an optional mapping from
775 - namespace prefix to full name.
776 + namespace prefix to full name. Pass ``None`` as prefix to move all
777 + unprefixed tag names in the expression into the given namespace.
776 778
777 779
778 780 .. method:: findtext(match, default=None, namespaces=None)
@@ -782,7 +784,8 @@ Element Objects
782 784 of the first matching element, or *default* if no element was found.
783 785 Note that if the matching element has no text content an empty string
784 786 is returned. *namespaces* is an optional mapping from namespace prefix
785 - to full name.
787 + to full name. Pass ``None`` as prefix to move all unprefixed tag names
788 + in the expression into the given namespace.
786 789
787 790
788 791 .. method:: getchildren()
Original file line number Diff line number Diff line change
@@ -2463,6 +2463,12 @@ def test_findall_different_nsmaps(self):
2463 2463 nsmap = {'xx': 'Y'}
2464 2464 self.assertEqual(len(root.findall(".//xx:b", namespaces=nsmap)), 1)
2465 2465 self.assertEqual(len(root.findall(".//b", namespaces=nsmap)), 2)
2466 +nsmap = {'xx': 'X', None: 'Y'}
2467 +self.assertEqual(len(root.findall(".//xx:b", namespaces=nsmap)), 2)
2468 +self.assertEqual(len(root.findall(".//b", namespaces=nsmap)), 1)
2469 +nsmap = {'xx': 'X', '': 'Y'}
2470 +with self.assertRaisesRegex(ValueError, 'namespace prefix'):
2471 +root.findall(".//xx:b", namespaces=nsmap)
2466 2472
2467 2473 def test_bad_find(self):
2468 2474 e = ET.XML(SAMPLE_XML)
Original file line number Diff line number Diff line change
@@ -71,16 +71,22 @@
71 71 )
72 72
73 73 def xpath_tokenizer(pattern, namespaces=None):
74 +default_namespace = namespaces.get(None) if namespaces else None
74 75 for token in xpath_tokenizer_re.findall(pattern):
75 76 tag = token[1]
76 -if tag and tag[0] != "{" and ":" in tag:
77 -try:
77 +if tag and tag[0] != "{":
78 +if ":" in tag:
78 79 prefix, uri = tag.split(":", 1)
79 -if not namespaces:
80 -raise KeyError
81 -yield token[0], "{%s}%s" % (namespaces[prefix], uri)
82 -except KeyError:
83 -raise SyntaxError("prefix %r not found in prefix map" % prefix) from None
80 +try:
81 +if not namespaces:
82 +raise KeyError
83 +yield token[0], "{%s}%s" % (namespaces[prefix], uri)
84 +except KeyError:
85 +raise SyntaxError("prefix %r not found in prefix map" % prefix) from None
86 +elif default_namespace:
87 +yield token[0], "{%s}%s" % (default_namespace, tag)
88 +else:
89 +yield token
84 90 else:
85 91 yield token
86 92
@@ -264,10 +270,19 @@ def __init__(self, root):
264 270
265 271 def iterfind(elem, path, namespaces=None):
266 272 # compile selector pattern
267 -cache_key = (path, None if namespaces is None
268 -else tuple(sorted(namespaces.items())))
269 273 if path[-1:] == "/":
270 274 path = path + "*" # implicit all (FIXME: keep this?)
275 +
276 +cache_key = (path,)
277 +if namespaces:
278 +if '' in namespaces:
279 +raise ValueError("empty namespace prefix must be passed as None, not the empty string")
280 +if None in namespaces:
281 +cache_key += (namespaces[None],) + tuple(sorted(
282 +item for item in namespaces.items() if item[0] is not None))
283 +else:
284 +cache_key += tuple(sorted(namespaces.items()))
285 +
271 286 try:
272 287 selector = _cache[cache_key]
273 288 except KeyError:
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1 +Path expressions in xml.etree.ElementTree can now avoid explicit namespace
2 +prefixes for tags (or the "{namespace}tag" notation) by passing a default
3 +namespace with a 'None' prefix.