Skip to content

Commit c472246

Browse files
Issue #10131: Fixed deep copying of minidom documents. Based on patch
by Marian Ganisin.
1 parent 747d48c commit c472246

File tree

4 files changed

+94
-45
lines changed

4 files changed

+94
-45
lines changed

Lib/test/test_minidom.py

Lines changed: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# test for xml.dom.minidom
22

3+
import copy
34
import pickle
45
from test.support import run_unittest, findfile
56
import unittest
@@ -11,6 +12,13 @@
1112

1213

1314
tstfile = findfile("test.xml", subdir="xmltestdata")
15+
sample = ("<?xml version='1.0' encoding='us-ascii'?>\n"
16+
"<!DOCTYPE doc PUBLIC 'http://xml.python.org/public'"
17+
" 'http://xml.python.org/system' [\n"
18+
" <!ELEMENT e EMPTY>\n"
19+
" <!ENTITY ent SYSTEM 'http://xml.python.org/entity'>\n"
20+
"]><doc attr='value'> text\n"
21+
"<?pi sample?> <!-- comment --> <e/> </doc>")
1422

1523
# The tests of DocumentType importing use these helpers to construct
1624
# the documents to work with, since not all DOM builders actually
@@ -1466,52 +1474,54 @@ def testSetIdAttributeNode(self):
14661474
self.confirm(e.isSameNode(doc.getElementById("w"))
14671475
and a2.isId)
14681476

1477+
def assert_recursive_equal(self, doc, doc2):
1478+
stack = [(doc, doc2)]
1479+
while stack:
1480+
n1, n2 = stack.pop()
1481+
self.assertEqual(n1.nodeType, n2.nodeType)
1482+
self.assertEqual(len(n1.childNodes), len(n2.childNodes))
1483+
self.assertEqual(n1.nodeName, n2.nodeName)
1484+
self.assertFalse(n1.isSameNode(n2))
1485+
self.assertFalse(n2.isSameNode(n1))
1486+
if n1.nodeType == Node.DOCUMENT_TYPE_NODE:
1487+
len(n1.entities)
1488+
len(n2.entities)
1489+
len(n1.notations)
1490+
len(n2.notations)
1491+
self.assertEqual(len(n1.entities), len(n2.entities))
1492+
self.assertEqual(len(n1.notations), len(n2.notations))
1493+
for i in range(len(n1.notations)):
1494+
# XXX this loop body doesn't seem to be executed?
1495+
no1 = n1.notations.item(i)
1496+
no2 = n1.notations.item(i)
1497+
self.assertEqual(no1.name, no2.name)
1498+
self.assertEqual(no1.publicId, no2.publicId)
1499+
self.assertEqual(no1.systemId, no2.systemId)
1500+
stack.append((no1, no2))
1501+
for i in range(len(n1.entities)):
1502+
e1 = n1.entities.item(i)
1503+
e2 = n2.entities.item(i)
1504+
self.assertEqual(e1.notationName, e2.notationName)
1505+
self.assertEqual(e1.publicId, e2.publicId)
1506+
self.assertEqual(e1.systemId, e2.systemId)
1507+
stack.append((e1, e2))
1508+
if n1.nodeType != Node.DOCUMENT_NODE:
1509+
self.assertTrue(n1.ownerDocument.isSameNode(doc))
1510+
self.assertTrue(n2.ownerDocument.isSameNode(doc2))
1511+
for i in range(len(n1.childNodes)):
1512+
stack.append((n1.childNodes[i], n2.childNodes[i]))
1513+
14691514
def testPickledDocument(self):
1470-
doc = parseString("<?xml version='1.0' encoding='us-ascii'?>\n"
1471-
"<!DOCTYPE doc PUBLIC 'http://xml.python.org/public'"
1472-
" 'http://xml.python.org/system' [\n"
1473-
" <!ELEMENT e EMPTY>\n"
1474-
" <!ENTITY ent SYSTEM 'http://xml.python.org/entity'>\n"
1475-
"]><doc attr='value'> text\n"
1476-
"<?pi sample?> <!-- comment --> <e/> </doc>")
1515+
doc = parseString(sample)
14771516
for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):
14781517
s = pickle.dumps(doc, proto)
14791518
doc2 = pickle.loads(s)
1480-
stack = [(doc, doc2)]
1481-
while stack:
1482-
n1, n2 = stack.pop()
1483-
self.confirm(n1.nodeType == n2.nodeType
1484-
and len(n1.childNodes) == len(n2.childNodes)
1485-
and n1.nodeName == n2.nodeName
1486-
and not n1.isSameNode(n2)
1487-
and not n2.isSameNode(n1))
1488-
if n1.nodeType == Node.DOCUMENT_TYPE_NODE:
1489-
len(n1.entities)
1490-
len(n2.entities)
1491-
len(n1.notations)
1492-
len(n2.notations)
1493-
self.confirm(len(n1.entities) == len(n2.entities)
1494-
and len(n1.notations) == len(n2.notations))
1495-
for i in range(len(n1.notations)):
1496-
# XXX this loop body doesn't seem to be executed?
1497-
no1 = n1.notations.item(i)
1498-
no2 = n1.notations.item(i)
1499-
self.confirm(no1.name == no2.name
1500-
and no1.publicId == no2.publicId
1501-
and no1.systemId == no2.systemId)
1502-
stack.append((no1, no2))
1503-
for i in range(len(n1.entities)):
1504-
e1 = n1.entities.item(i)
1505-
e2 = n2.entities.item(i)
1506-
self.confirm(e1.notationName == e2.notationName
1507-
and e1.publicId == e2.publicId
1508-
and e1.systemId == e2.systemId)
1509-
stack.append((e1, e2))
1510-
if n1.nodeType != Node.DOCUMENT_NODE:
1511-
self.confirm(n1.ownerDocument.isSameNode(doc)
1512-
and n2.ownerDocument.isSameNode(doc2))
1513-
for i in range(len(n1.childNodes)):
1514-
stack.append((n1.childNodes[i], n2.childNodes[i]))
1519+
self.assert_recursive_equal(doc, doc2)
1520+
1521+
def testDeepcopiedDocument(self):
1522+
doc = parseString(sample)
1523+
doc2 = copy.deepcopy(doc)
1524+
self.assert_recursive_equal(doc, doc2)
15151525

15161526
def testSerializeCommentNodeWithDoubleHyphen(self):
15171527
doc = create_doc_without_doctype()

Lib/test/test_xml_dom_minicompat.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Tests for xml.dom.minicompat
22

3+
import copy
34
import pickle
45
import unittest
56

@@ -89,14 +90,49 @@ def test_nodelist_pickle_roundtrip(self):
8990
node_list = NodeList()
9091
pickled = pickle.dumps(node_list, proto)
9192
unpickled = pickle.loads(pickled)
93+
self.assertIsNot(unpickled, node_list)
9294
self.assertEqual(unpickled, node_list)
9395

9496
# Non-empty NodeList.
9597
node_list.append(1)
9698
node_list.append(2)
9799
pickled = pickle.dumps(node_list, proto)
98100
unpickled = pickle.loads(pickled)
101+
self.assertIsNot(unpickled, node_list)
99102
self.assertEqual(unpickled, node_list)
100103

104+
def test_nodelist_copy(self):
105+
# Empty NodeList.
106+
node_list = NodeList()
107+
copied = copy.copy(node_list)
108+
self.assertIsNot(copied, node_list)
109+
self.assertEqual(copied, node_list)
110+
111+
# Non-empty NodeList.
112+
node_list.append([1])
113+
node_list.append([2])
114+
copied = copy.copy(node_list)
115+
self.assertIsNot(copied, node_list)
116+
self.assertEqual(copied, node_list)
117+
for x, y in zip(copied, node_list):
118+
self.assertIs(x, y)
119+
120+
def test_nodelist_deepcopy(self):
121+
# Empty NodeList.
122+
node_list = NodeList()
123+
copied = copy.deepcopy(node_list)
124+
self.assertIsNot(copied, node_list)
125+
self.assertEqual(copied, node_list)
126+
127+
# Non-empty NodeList.
128+
node_list.append([1])
129+
node_list.append([2])
130+
copied = copy.deepcopy(node_list)
131+
self.assertIsNot(copied, node_list)
132+
self.assertEqual(copied, node_list)
133+
for x, y in zip(copied, node_list):
134+
self.assertIsNot(x, y)
135+
self.assertEqual(x, y)
136+
101137
if __name__ == '__main__':
102138
unittest.main()

Lib/xml/dom/minicompat.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ def _set_length(self, value):
6464
length = property(_get_length, _set_length,
6565
doc="The number of nodes in the NodeList.")
6666

67-
def __getstate__(self):
68-
return list(self)
69-
67+
# For backward compatibility
7068
def __setstate__(self, state):
69+
if state is None:
70+
state = []
7171
self[:] = state
7272

7373

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ Core and Builtins
113113
Library
114114
-------
115115

116+
- Issue #10131: Fixed deep copying of minidom documents. Based on patch
117+
by Marian Ganisin.
118+
116119
- Issue #25725: Fixed a reference leak in pickle.loads() when unpickling
117120
invalid data including tuple instructions.
118121

0 commit comments

Comments
 (0)