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
6 changes: 6 additions & 0 deletions Doc/library/xml.dom.rst
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,12 @@ inherits properties from :class:`Node`.
Return the element with the given ID, or ``None``.
Only attributes declared as being of type ID in the DTD
or by :meth:`Element.setIdAttribute` are searched.
If several elements have the same ID, the first one
in document order is returned.

.. versionchanged:: next
Previously, which of the elements with the same ID was returned
was unspecified and could change between calls.


.. method:: Document.getElementsByTagName(tagName)
Expand Down
36 changes: 36 additions & 0 deletions Lib/test/test_minidom.py
Original file line number Diff line number Diff line change
Expand Up @@ -2143,6 +2143,42 @@ def testSetIdAttributeNode(self):
self.confirm(e.isSameNode(doc.getElementById("w"))
and a2.isId)

def check_duplicate_id(self, doc):
# The first element in document order is returned, and lookups
# of other IDs which continue the search do not change that.
for i in range(2):
self.assertEqual(doc.getElementById("x").getAttribute("n"), "1")
self.assertEqual(doc.getElementById("y").getAttribute("n"), "0")
self.assertEqual(doc.getElementById("x").getAttribute("n"), "1")
self.assertIsNone(doc.getElementById("missing"))
self.assertEqual(doc.getElementById("x").getAttribute("n"), "1")
self.assertEqual(doc.getElementById("z").getAttribute("n"), "4")
# clear the cache and start the search again
doc.documentElement.setAttribute("a", str(i))

def testGetElementByIdDuplicate(self):
body = ("<doc>"
"<e id='y' n='0'/>"
"<f><e id='x' n='1'/></f>"
"<e id='x' n='2'><e id='x' n='3'/></e>"
"<e id='z' n='4'/>"
"</doc>")
with self.subTest("ATTLIST"):
doc = parseString("<!DOCTYPE doc [<!ATTLIST e id ID #IMPLIED>]>" +
body)
self.check_duplicate_id(doc)
with self.subTest("setIdAttribute"):
doc = parseString(body)
for e in doc.getElementsByTagName("e"):
e.setIdAttribute("id")
self.check_duplicate_id(doc)
with self.subTest("ATTLIST and setIdAttribute"):
doc = parseString("<!DOCTYPE doc [<!ATTLIST e n CDATA #IMPLIED>]>" +
body)
for e in doc.getElementsByTagName("e"):
e.setIdAttribute("id")
self.check_duplicate_id(doc)

def assert_recursive_equal(self, doc, doc2):
stack = [(doc, doc2)]
while stack:
Expand Down
13 changes: 7 additions & 6 deletions Lib/xml/dom/minidom.py
Original file line number Diff line number Diff line change
Expand Up @@ -2030,8 +2030,9 @@ def getElementById(self, id):
result = None
while stack:
node = stack.pop()
# add child elements to stack for continued searching
stack.extend([child for child in node.childNodes
# add child elements to stack for continued searching,
# last child first so that they are visited in document order
stack.extend([child for child in reversed(node.childNodes)
if child.nodeType in _nodeTypes_with_children])
# check this node
info = self._get_elem_info(node)
Expand All @@ -2042,27 +2043,27 @@ def getElementById(self, id):
for attr in node.attributes.values():
if attr.namespaceURI:
if info.isIdNS(attr.namespaceURI, attr.localName):
self._id_cache[attr.value] = node
self._id_cache.setdefault(attr.value, node)
if attr.value == id:
result = node
elif not node._magic_id_nodes:
break
elif info.isId(attr.name):
self._id_cache[attr.value] = node
self._id_cache.setdefault(attr.value, node)
if attr.value == id:
result = node
elif not node._magic_id_nodes:
break
elif attr._is_id:
self._id_cache[attr.value] = node
self._id_cache.setdefault(attr.value, node)
if attr.value == id:
result = node
elif node._magic_id_nodes == 1:
break
elif node._magic_id_nodes:
for attr in node.attributes.values():
if attr._is_id:
self._id_cache[attr.value] = node
self._id_cache.setdefault(attr.value, node)
if attr.value == id:
result = node
if result is not None:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix :meth:`~xml.dom.Document.getElementById` in :mod:`xml.dom.minidom` so
that it returns the first element in document order when several elements
have the same ID. Previously, the result could differ between calls on an
unmodified document.
Loading