Skip to content

Commit 37f5421

Browse files
Issue #10131: Fixed deep copying of minidom documents. Based on patch
by Marian Ganisin.
2 parents 2ed41f3 + 15f070f commit 37f5421

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 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
@@ -1481,52 +1489,54 @@ def testSetIdAttributeNode(self):
14811489
self.confirm(e.isSameNode(doc.getElementById("w"))
14821490
and a2.isId)
14831491

1492+
def assert_recursive_equal(self, doc, doc2):
1493+
stack = [(doc, doc2)]
1494+
while stack:
1495+
n1, n2 = stack.pop()
1496+
self.assertEqual(n1.nodeType, n2.nodeType)
1497+
self.assertEqual(len(n1.childNodes), len(n2.childNodes))
1498+
self.assertEqual(n1.nodeName, n2.nodeName)
1499+
self.assertFalse(n1.isSameNode(n2))
1500+
self.assertFalse(n2.isSameNode(n1))
1501+
if n1.nodeType == Node.DOCUMENT_TYPE_NODE:
1502+
len(n1.entities)
1503+
len(n2.entities)
1504+
len(n1.notations)
1505+
len(n2.notations)
1506+
self.assertEqual(len(n1.entities), len(n2.entities))
1507+
self.assertEqual(len(n1.notations), len(n2.notations))
1508+
for i in range(len(n1.notations)):
1509+
# XXX this loop body doesn't seem to be executed?
1510+
no1 = n1.notations.item(i)
1511+
no2 = n1.notations.item(i)
1512+
self.assertEqual(no1.name, no2.name)
1513+
self.assertEqual(no1.publicId, no2.publicId)
1514+
self.assertEqual(no1.systemId, no2.systemId)
1515+
stack.append((no1, no2))
1516+
for i in range(len(n1.entities)):
1517+
e1 = n1.entities.item(i)
1518+
e2 = n2.entities.item(i)
1519+
self.assertEqual(e1.notationName, e2.notationName)
1520+
self.assertEqual(e1.publicId, e2.publicId)
1521+
self.assertEqual(e1.systemId, e2.systemId)
1522+
stack.append((e1, e2))
1523+
if n1.nodeType != Node.DOCUMENT_NODE:
1524+
self.assertTrue(n1.ownerDocument.isSameNode(doc))
1525+
self.assertTrue(n2.ownerDocument.isSameNode(doc2))
1526+
for i in range(len(n1.childNodes)):
1527+
stack.append((n1.childNodes[i], n2.childNodes[i]))
1528+
14841529
def testPickledDocument(self):
1485-
doc = parseString("<?xml version='1.0' encoding='us-ascii'?>\n"
1486-
"<!DOCTYPE doc PUBLIC 'http://xml.python.org/public'"
1487-
" 'http://xml.python.org/system' [\n"
1488-
" <!ELEMENT e EMPTY>\n"
1489-
" <!ENTITY ent SYSTEM 'http://xml.python.org/entity'>\n"
1490-
"]><doc attr='value'> text\n"
1491-
"<?pi sample?> <!-- comment --> <e/> </doc>")
1530+
doc = parseString(sample)
14921531
for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):
14931532
s = pickle.dumps(doc, proto)
14941533
doc2 = pickle.loads(s)
1495-
stack = [(doc, doc2)]
1496-
while stack:
1497-
n1, n2 = stack.pop()
1498-
self.confirm(n1.nodeType == n2.nodeType
1499-
and len(n1.childNodes) == len(n2.childNodes)
1500-
and n1.nodeName == n2.nodeName
1501-
and not n1.isSameNode(n2)
1502-
and not n2.isSameNode(n1))
1503-
if n1.nodeType == Node.DOCUMENT_TYPE_NODE:
1504-
len(n1.entities)
1505-
len(n2.entities)
1506-
len(n1.notations)
1507-
len(n2.notations)
1508-
self.confirm(len(n1.entities) == len(n2.entities)
1509-
and len(n1.notations) == len(n2.notations))
1510-
for i in range(len(n1.notations)):
1511-
# XXX this loop body doesn't seem to be executed?
1512-
no1 = n1.notations.item(i)
1513-
no2 = n1.notations.item(i)
1514-
self.confirm(no1.name == no2.name
1515-
and no1.publicId == no2.publicId
1516-
and no1.systemId == no2.systemId)
1517-
stack.append((no1, no2))
1518-
for i in range(len(n1.entities)):
1519-
e1 = n1.entities.item(i)
1520-
e2 = n2.entities.item(i)
1521-
self.confirm(e1.notationName == e2.notationName
1522-
and e1.publicId == e2.publicId
1523-
and e1.systemId == e2.systemId)
1524-
stack.append((e1, e2))
1525-
if n1.nodeType != Node.DOCUMENT_NODE:
1526-
self.confirm(n1.ownerDocument.isSameNode(doc)
1527-
and n2.ownerDocument.isSameNode(doc2))
1528-
for i in range(len(n1.childNodes)):
1529-
stack.append((n1.childNodes[i], n2.childNodes[i]))
1534+
self.assert_recursive_equal(doc, doc2)
1535+
1536+
def testDeepcopiedDocument(self):
1537+
doc = parseString(sample)
1538+
doc2 = copy.deepcopy(doc)
1539+
self.assert_recursive_equal(doc, doc2)
15301540

15311541
def testSerializeCommentNodeWithDoubleHyphen(self):
15321542
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
@@ -102,6 +102,9 @@ Core and Builtins
102102
Library
103103
-------
104104

105+
- Issue #10131: Fixed deep copying of minidom documents. Based on patch
106+
by Marian Ganisin.
107+
105108
- Issue #7990: dir() on ElementTree.Element now lists properties: "tag",
106109
"text", "tail" and "attrib". Original patch by Santoso Wijaya.
107110

0 commit comments

Comments
 (0)