From 91e4cad210ec52f686e84c34885cd178db2b9f0d Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 27 Oct 2018 21:26:12 -0700 Subject: [PATCH 1/2] Preserve user specified order of Element attributes --- Doc/library/xml.etree.elementtree.rst | 8 ++++++ Lib/test/test_xml_etree.py | 25 +++++++++++++++++++ Lib/xml/etree/ElementTree.py | 2 +- .../2018-10-27-21-11-42.bpo-34160.UzyPZf.rst | 1 + 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2018-10-27-21-11-42.bpo-34160.UzyPZf.rst diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index 0cb949a9f481c35..aae80140374cc1e 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -489,6 +489,10 @@ Functions *elem* is an element tree or an individual element. + .. versionchanged:: 3.8 + The :func:`dump` function now preserves the attribute order specified + by the user. + .. function:: fromstring(text) @@ -947,6 +951,10 @@ ElementTree Objects .. versionadded:: 3.4 The *short_empty_elements* parameter. + .. versionchanged:: 3.8 + The :meth:`write` method now preserves the attribute order specified + by the user. + This is the XML file that is going to be manipulated:: diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 8b16905087971e6..99ca7b4f9775c41 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -1044,6 +1044,31 @@ def test_html_empty_elems_serialization(self): method='html') self.assertEqual(serialized, expected) + def test_dump_attribute_order(self): + # See BPO 34160 + e = ET.Element('cirriculum', status='public', company='example') + with support.captured_stdout() as stdout: + ET.dump(e) + self.assertEqual(stdout.getvalue(), + '\n') + + def test_tree_write_attribute_order(self): + # See BPO 34160 + root = ET.Element('cirriculum', status='public', company='example') + tree = ET.ElementTree(root) + try: + fo = open(support.TESTFN, "wb") + tree.write(fo, encoding='utf-8', xml_declaration=True) + fo.close() + fo = open(support.TESTFN, "r") + text = fo.read() + finally: + fo.close() + support.unlink(support.TESTFN) + self.assertEqual(text, + "\n" + '') + class XMLPullParserTest(unittest.TestCase): diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 85586d0b1c49319..710b8b7f74256be 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -923,7 +923,7 @@ def _serialize_xml(write, elem, qnames, namespaces, k, _escape_attrib(v) )) - for k, v in sorted(items): # lexical order + for k, v in items: # lexical order if isinstance(k, QName): k = k.text if isinstance(v, QName): diff --git a/Misc/NEWS.d/next/Library/2018-10-27-21-11-42.bpo-34160.UzyPZf.rst b/Misc/NEWS.d/next/Library/2018-10-27-21-11-42.bpo-34160.UzyPZf.rst new file mode 100644 index 000000000000000..6f3c076d3c0321f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-10-27-21-11-42.bpo-34160.UzyPZf.rst @@ -0,0 +1 @@ +ElementTree now preserves the attribute order specified by the user. From 6a1b7eca24a8e259ed3e1acc70b5405aea1170c1 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 27 Oct 2018 23:40:30 -0700 Subject: [PATCH 2/2] Tighten test code. Remove outdated comment. --- Lib/test/test_xml_etree.py | 19 +++++++------------ Lib/xml/etree/ElementTree.py | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 99ca7b4f9775c41..9988dad86384e0f 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -5,6 +5,7 @@ # For this purpose, the module-level "ET" symbol is temporarily # monkey-patched when running the "test_xml_etree_c" test suite. +import contextlib import copy import functools import html @@ -1056,18 +1057,12 @@ def test_tree_write_attribute_order(self): # See BPO 34160 root = ET.Element('cirriculum', status='public', company='example') tree = ET.ElementTree(root) - try: - fo = open(support.TESTFN, "wb") - tree.write(fo, encoding='utf-8', xml_declaration=True) - fo.close() - fo = open(support.TESTFN, "r") - text = fo.read() - finally: - fo.close() - support.unlink(support.TESTFN) - self.assertEqual(text, - "\n" - '') + f = io.BytesIO() + with contextlib.redirect_stdout(f): + tree.write(f, encoding='utf-8', xml_declaration=True) + self.assertEqual(f.getvalue(), + b"\n" + b'') class XMLPullParserTest(unittest.TestCase): diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 710b8b7f74256be..d4df83fb51ccd97 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -923,7 +923,7 @@ def _serialize_xml(write, elem, qnames, namespaces, k, _escape_attrib(v) )) - for k, v in items: # lexical order + for k, v in items: if isinstance(k, QName): k = k.text if isinstance(v, QName):