diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py index 446bbe096bd19dc..b19a0cfd94b8ddd 100644 --- a/Lib/test/test_minidom.py +++ b/Lib/test/test_minidom.py @@ -1605,6 +1605,21 @@ def testNormalizeDeleteAndCombine(self): , "testNormalizeDeleteAndCombine -- result") doc.unlink() + def testNormalizeManyTextNodes(self): + # Ensure that normalize() is fast with many adjacent text nodes. + N = 100_000 + doc = parseString("") + root = doc.documentElement + for i in range(N): + root.appendChild(doc.createTextNode("x" * 16)) + if i % 3 == 0: + root.appendChild(doc.createTextNode("")) + doc.normalize() + self.assertEqual(len(root.childNodes), 1) + self.assertEqual(root.firstChild.data, "x" * 16 * N) + self.assertIsNone(root.firstChild.nextSibling) + doc.unlink() + def testNormalizeRecursion(self): doc = parseString("" "" diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 7cb652a323dcc22..d4166a2bce20e5e 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -196,6 +196,9 @@ def removeChild(self, oldChild): def normalize(self): L = [] + # (text node, [data, ...]) for each text node that absorbs others. + # Join each run once at the end; concatenating as we go is quadratic. + runs = None for child in self.childNodes: if child.nodeType == Node.TEXT_NODE: if not child.data: @@ -208,7 +211,12 @@ def normalize(self): elif L and L[-1].nodeType == child.nodeType: # collapse text node node = L[-1] - node.data = node.data + child.data + if runs is None: + runs = [] + if runs and runs[-1][0] is node: + runs[-1][1].append(child.data) + else: + runs.append((node, [node.data, child.data])) node.nextSibling = child.nextSibling if child.nextSibling: child.nextSibling.previousSibling = node @@ -219,6 +227,9 @@ def normalize(self): L.append(child) if child.nodeType == Node.ELEMENT_NODE: child.normalize() + if runs is not None: + for node, data in runs: + node.data = ''.join(data) self.childNodes[:] = L def cloneNode(self, deep): diff --git a/Misc/NEWS.d/next/Library/2026-10-05-16-40-00.gh-issue-158860.Mn7dXk.rst b/Misc/NEWS.d/next/Library/2026-10-05-16-40-00.gh-issue-158860.Mn7dXk.rst new file mode 100644 index 000000000000000..3e4051e21beee0c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-05-16-40-00.gh-issue-158860.Mn7dXk.rst @@ -0,0 +1,2 @@ +:meth:`xml.dom.minidom.Node.normalize` now merges runs of adjacent text +nodes in linear time instead of quadratic time.