Skip to content

Commit 54a3ade

Browse files
committed
gh-158978: Fix cloning of DOM Level 1 nodes in xml.dom.minidom
cloneNode() and importNode() created the clones with createElementNS(), setAttributeNS() and createAttributeNS(), which now reject a prefixed name in no namespace. Create the Element and Attr nodes directly instead.
1 parent 9d22a53 commit 54a3ade

2 files changed

Lines changed: 98 additions & 6 deletions

File tree

‎Lib/test/test_minidom.py‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from test import support
77
import unittest
88

9+
import xml.dom.expatbuilder
910
import xml.dom.minidom
1011

1112
from xml.dom.minidom import parse, Attr, Node, Document, Element, parseString
@@ -1277,6 +1278,65 @@ def testCloneElementDeep(self):
12771278
self.assertTrue(clone.toxml(), '<doc attr="value"><foo/></doc>')
12781279
dom.unlink()
12791280

1281+
def create_dom1_element(self):
1282+
# DOM Level 1 nodes have no namespace, but their names can contain
1283+
# a colon.
1284+
doc = Document()
1285+
root = doc.appendChild(doc.createElement("root"))
1286+
root.setAttribute("xmlns", "http://xml.python.org/default")
1287+
root.setAttribute("xmlns:xsi", "http://xml.python.org/xsi")
1288+
root.setAttribute("xsi:type", "t")
1289+
root.appendChild(doc.createElement("svg:rect"))
1290+
return doc, root
1291+
1292+
def check_dom1_names(self, node, nodeName, prefix, localName):
1293+
self.assertEqual(node.nodeName, nodeName)
1294+
self.assertEqual(node.prefix, prefix)
1295+
self.assertEqual(node.localName, localName)
1296+
self.assertIsNone(node.namespaceURI)
1297+
1298+
def check_dom1_element(self, elem, doc):
1299+
self.assertIs(elem.ownerDocument, doc)
1300+
self.check_dom1_names(elem, "root", None, "root")
1301+
self.assertEqual(len(elem.attributes), 3)
1302+
for name, prefix, localName, value in [
1303+
("xmlns", None, "xmlns", "http://xml.python.org/default"),
1304+
("xmlns:xsi", "xmlns", "xsi", "http://xml.python.org/xsi"),
1305+
("xsi:type", "xsi", "type", "t"),
1306+
]:
1307+
with self.subTest(name=name):
1308+
attr = elem.getAttributeNode(name)
1309+
self.check_dom1_names(attr, name, prefix, localName)
1310+
self.assertEqual(attr.value, value)
1311+
self.assertIs(attr.ownerElement, elem)
1312+
self.assertIs(attr.ownerDocument, doc)
1313+
self.assertEqual(len(elem.childNodes), 1)
1314+
self.check_dom1_names(elem.firstChild, "svg:rect", "svg", "rect")
1315+
self.assertIs(elem.firstChild.ownerDocument, doc)
1316+
1317+
def testCloneElementDOM1Names(self):
1318+
doc, root = self.create_dom1_element()
1319+
clone = root.cloneNode(True)
1320+
self.assertIsNot(clone, root)
1321+
self.assertIsNone(clone.parentNode)
1322+
self.check_dom1_element(clone, doc)
1323+
doc.unlink()
1324+
1325+
def testCloneElementNoNamespaces(self):
1326+
doc = xml.dom.expatbuilder.parseString(
1327+
"<a:b xmlns:a='http://xml.python.org/a' a:x='1'><a:c/></a:b>",
1328+
namespaces=False)
1329+
clone = doc.documentElement.cloneNode(True)
1330+
self.check_dom1_names(clone, "a:b", "a", "b")
1331+
self.assertEqual(clone.getAttribute("xmlns:a"),
1332+
"http://xml.python.org/a")
1333+
self.check_dom1_names(clone.getAttributeNode("xmlns:a"),
1334+
"xmlns:a", "xmlns", "a")
1335+
self.assertEqual(clone.getAttribute("a:x"), "1")
1336+
self.check_dom1_names(clone.getAttributeNode("a:x"), "a:x", "a", "x")
1337+
self.check_dom1_names(clone.firstChild, "a:c", "a", "c")
1338+
doc.unlink()
1339+
12801340
def testCloneDocumentShallow(self):
12811341
doc = parseString("<?xml version='1.0'?>\n"
12821342
"<!-- comment -->"
@@ -1395,6 +1455,15 @@ def testImportDocumentTypeDeep(self):
13951455
self.assertRaises(xml.dom.NotSupportedErr, target.importNode,
13961456
src.doctype, 1)
13971457

1458+
def testImportElementDOM1Names(self):
1459+
doc1, root = self.create_dom1_element()
1460+
doc2 = Document()
1461+
imported = doc2.importNode(root, True)
1462+
self.assertIsNone(imported.parentNode)
1463+
self.check_dom1_element(imported, doc2)
1464+
doc1.unlink()
1465+
doc2.unlink()
1466+
13981467
# Testing attribute clones uses a helper, and should always be deep,
13991468
# even if the argument to cloneNode is false.
14001469
def check_clone_attribute(self, deep, testName):
@@ -1417,6 +1486,24 @@ def testCloneAttributeShallow(self):
14171486
def testCloneAttributeDeep(self):
14181487
self.check_clone_attribute(1, "testCloneAttributeDeep")
14191488

1489+
def testCloneAttributeDOM1Names(self):
1490+
doc1 = Document()
1491+
attr = doc1.createAttribute("a:b")
1492+
attr.value = "v"
1493+
doc2 = Document()
1494+
for method, clone, owner in [
1495+
("cloneNode", attr.cloneNode(True), doc1),
1496+
("importNode", doc2.importNode(attr, True), doc2),
1497+
]:
1498+
with self.subTest(method=method):
1499+
self.check_dom1_names(clone, "a:b", "a", "b")
1500+
self.assertEqual(clone.value, "v")
1501+
self.assertTrue(clone.specified)
1502+
self.assertIsNone(clone.ownerElement)
1503+
self.assertIs(clone.ownerDocument, owner)
1504+
doc1.unlink()
1505+
doc2.unlink()
1506+
14201507
def check_clone_pi(self, deep, testName):
14211508
doc = parseString("<?target data?><doc/>")
14221509
pi = doc.firstChild

‎Lib/xml/dom/minidom.py‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2169,11 +2169,15 @@ def _clone_node(node, deep, newOwnerDocument):
21692169
else:
21702170
operation = xml.dom.UserDataHandler.NODE_IMPORTED
21712171
if node.nodeType == Node.ELEMENT_NODE:
2172-
clone = newOwnerDocument.createElementNS(node.namespaceURI,
2173-
node.nodeName)
2172+
prefix, localName = _nssplit(node.nodeName)
2173+
clone = Element(node.nodeName, node.namespaceURI, prefix)
2174+
clone.ownerDocument = newOwnerDocument
21742175
for attr in node.attributes.values():
2175-
clone.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value)
2176-
a = clone.getAttributeNodeNS(attr.namespaceURI, attr.localName)
2176+
prefix, localName = _nssplit(attr.nodeName)
2177+
a = Attr(attr.nodeName, attr.namespaceURI, localName, prefix)
2178+
a.value = attr.value
2179+
a.ownerDocument = newOwnerDocument
2180+
clone.setAttributeNode(a)
21772181
a.specified = attr.specified
21782182

21792183
if deep:
@@ -2200,8 +2204,9 @@ def _clone_node(node, deep, newOwnerDocument):
22002204
elif node.nodeType == Node.ENTITY_REFERENCE_NODE:
22012205
clone = newOwnerDocument.createEntityReference(node.nodeName)
22022206
elif node.nodeType == Node.ATTRIBUTE_NODE:
2203-
clone = newOwnerDocument.createAttributeNS(node.namespaceURI,
2204-
node.nodeName)
2207+
prefix, localName = _nssplit(node.nodeName)
2208+
clone = Attr(node.nodeName, node.namespaceURI, localName, prefix)
2209+
clone.ownerDocument = newOwnerDocument
22052210
clone.specified = True
22062211
clone.value = node.value
22072212
elif node.nodeType == Node.DOCUMENT_TYPE_NODE:

0 commit comments

Comments
 (0)