From a6c78cee61a264f003c0b4a20c6dfb3e47eb22e7 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Sat, 8 Dec 2018 16:16:18 +0100 Subject: [PATCH 01/12] Include rejected unqualified name in exception from ElementTree.write() --- Lib/test/test_xml_etree.py | 5 ++--- Lib/xml/etree/ElementTree.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 8a7ec0076ff0368..7e8585fcae0f377 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -1627,10 +1627,9 @@ def test_bug_200709_default_namespace(self): e = ET.Element("{default}elem") s = ET.SubElement(e, "{default}elem") s = ET.SubElement(e, "elem") # unprefixed name - with self.assertRaises(ValueError) as cm: + with self.assertRaisesRegex(ValueError, + 'cannot use non-qualified name.* with default_namespace option'): serialize(e, default_namespace="default") # 3 - self.assertEqual(str(cm.exception), - 'cannot use non-qualified names with default_namespace option') def test_bug_200709_register_namespace(self): e = ET.Element("{http://namespace.invalid/does/not/exist/}title") diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index c1cf483cf56bb26..a84970a0c85ec19 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -864,8 +864,8 @@ def add_qname(qname): if default_namespace: # FIXME: can this be handled in XML 1.0? raise ValueError( - "cannot use non-qualified names with " - "default_namespace option" + "cannot use non-qualified name (%s) with " + "default_namespace option" % qname ) qnames[qname] = qname except TypeError: From e0d6f6ae8f76b2b2156f28169085a9d4f57f20b2 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Sun, 9 Dec 2018 09:32:08 +0100 Subject: [PATCH 02/12] Rename internal variable for clarity The 'qname' argument is not only used for element names, so calling the non-namespace part 'tag' is confusing. The XML namespaces spec calls it 'local part', so I picked 'local' as the new name. --- Lib/xml/etree/ElementTree.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index a84970a0c85ec19..0a6da49a19d0fd6 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -848,7 +848,7 @@ def add_qname(qname): # calculate serialized qname representation try: if qname[:1] == "{": - uri, tag = qname[1:].rsplit("}", 1) + uri, local = qname[1:].rsplit("}", 1) prefix = namespaces.get(uri) if prefix is None: prefix = _namespace_map.get(uri) @@ -857,9 +857,9 @@ def add_qname(qname): if prefix != "xml": namespaces[uri] = prefix if prefix: - qnames[qname] = "%s:%s" % (prefix, tag) + qnames[qname] = "%s:%s" % (prefix, local) else: - qnames[qname] = tag # default element + qnames[qname] = local # default element else: if default_namespace: # FIXME: can this be handled in XML 1.0? From 5e200cf64a0139663050fab71ff63df0f557ba89 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Sun, 9 Dec 2018 11:35:25 +0100 Subject: [PATCH 03/12] Move handling of invalid attribute name types out of add_qname() All other callers of add_qname() already check the argument type prior to calling, so by also checking before adding an attribute name, add_qname() doesn't need to deal with TypeError anymore. The diff is actually tiny when whitespace changes are ignored. --- Lib/xml/etree/ElementTree.py | 43 ++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 0a6da49a19d0fd6..3917a87bb03d0e6 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -846,30 +846,27 @@ def _namespaces(elem, default_namespace=None): def add_qname(qname): # calculate serialized qname representation - try: - if qname[:1] == "{": - uri, local = qname[1:].rsplit("}", 1) - prefix = namespaces.get(uri) + if qname[:1] == "{": + uri, local = qname[1:].rsplit("}", 1) + prefix = namespaces.get(uri) + if prefix is None: + prefix = _namespace_map.get(uri) if prefix is None: - prefix = _namespace_map.get(uri) - if prefix is None: - prefix = "ns%d" % len(namespaces) - if prefix != "xml": - namespaces[uri] = prefix - if prefix: - qnames[qname] = "%s:%s" % (prefix, local) - else: - qnames[qname] = local # default element + prefix = "ns%d" % len(namespaces) + if prefix != "xml": + namespaces[uri] = prefix + if prefix: + qnames[qname] = "%s:%s" % (prefix, local) else: - if default_namespace: - # FIXME: can this be handled in XML 1.0? - raise ValueError( - "cannot use non-qualified name (%s) with " - "default_namespace option" % qname - ) - qnames[qname] = qname - except TypeError: - _raise_serialization_error(qname) + qnames[qname] = local # default element + else: + if default_namespace: + # FIXME: can this be handled in XML 1.0? + raise ValueError( + "cannot use non-qualified name (%s) with " + "default_namespace option" % qname + ) + qnames[qname] = qname # populate qname and namespaces table for elem in elem.iter(): @@ -885,6 +882,8 @@ def add_qname(qname): for key, value in elem.items(): if isinstance(key, QName): key = key.text + elif not isinstance(key, str): + _raise_serialization_error(key) if key not in qnames: add_qname(key) if isinstance(value, QName) and value.text not in qnames: From 34e6b95295937ef250b09cd9381a54c138eed4d6 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Sun, 9 Dec 2018 10:57:01 +0100 Subject: [PATCH 04/12] Separate serialized name computation from cache population This is in preparation of a future change that requires the default namespace to not be applied to attributes. The cache population code will become non-trivial then. --- Lib/xml/etree/ElementTree.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 3917a87bb03d0e6..8d38a81da8f2749 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -844,7 +844,7 @@ def _namespaces(elem, default_namespace=None): if default_namespace: namespaces[default_namespace] = "" - def add_qname(qname): + def serialize_qname(qname): # calculate serialized qname representation if qname[:1] == "{": uri, local = qname[1:].rsplit("}", 1) @@ -856,9 +856,9 @@ def add_qname(qname): if prefix != "xml": namespaces[uri] = prefix if prefix: - qnames[qname] = "%s:%s" % (prefix, local) + return "%s:%s" % (prefix, local) else: - qnames[qname] = local # default element + return local # default element else: if default_namespace: # FIXME: can this be handled in XML 1.0? @@ -866,17 +866,19 @@ def add_qname(qname): "cannot use non-qualified name (%s) with " "default_namespace option" % qname ) - qnames[qname] = qname + return qname + + def add_qname(qname): + if qname not in qnames: + qnames[qname] = serialize_qname(qname) # populate qname and namespaces table for elem in elem.iter(): tag = elem.tag if isinstance(tag, QName): - if tag.text not in qnames: - add_qname(tag.text) + add_qname(tag.text) elif isinstance(tag, str): - if tag not in qnames: - add_qname(tag) + add_qname(tag) elif tag is not None and tag is not Comment and tag is not PI: _raise_serialization_error(tag) for key, value in elem.items(): @@ -884,12 +886,11 @@ def add_qname(qname): key = key.text elif not isinstance(key, str): _raise_serialization_error(key) - if key not in qnames: - add_qname(key) - if isinstance(value, QName) and value.text not in qnames: + add_qname(key) + if isinstance(value, QName): add_qname(value.text) text = elem.text - if isinstance(text, QName) and text.text not in qnames: + if isinstance(text, QName): add_qname(text.text) return qnames, namespaces From 71119f5f0a9515cab86d31f608e93c32ad99c3e6 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Sun, 9 Dec 2018 10:06:37 +0100 Subject: [PATCH 05/12] Store separate local:prefix string in qnames cache for attributes This is in preparation of a future change that requires the default namespace to not be applied to attributes. --- Lib/xml/etree/ElementTree.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 8d38a81da8f2749..9bb5458a6362168 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -836,8 +836,10 @@ def _get_writer(file_or_filename, encoding): def _namespaces(elem, default_namespace=None): # identify namespaces used in this tree - # maps qnames to *encoded* prefix:local names - qnames = {None: None} + # maps qnames to *encoded* prefix:local names, + # value is a pair of prefix:local strings, + # second value is for attribute names + qnames = {None: (None, None)} # maps uri:s to prefixes namespaces = {} @@ -870,7 +872,8 @@ def serialize_qname(qname): def add_qname(qname): if qname not in qnames: - qnames[qname] = serialize_qname(qname) + serialized = serialize_qname(qname) + qnames[qname] = (serialized, serialized) # populate qname and namespaces table for elem in elem.iter(): @@ -903,7 +906,7 @@ def _serialize_xml(write, elem, qnames, namespaces, elif tag is ProcessingInstruction: write("" % text) else: - tag = qnames[tag] + tag = qnames[tag][0] if tag is None: if text: write(_escape_cdata(text)) @@ -927,10 +930,10 @@ def _serialize_xml(write, elem, qnames, namespaces, if isinstance(k, QName): k = k.text if isinstance(v, QName): - v = qnames[v.text] + v = qnames[v.text][0] else: v = _escape_attrib(v) - write(" %s=\"%s\"" % (qnames[k], v)) + write(" %s=\"%s\"" % (qnames[k][1], v)) if text or len(elem) or not short_empty_elements: write(">") if text: @@ -960,7 +963,7 @@ def _serialize_html(write, elem, qnames, namespaces, **kwargs): elif tag is ProcessingInstruction: write("" % _escape_cdata(text)) else: - tag = qnames[tag] + tag = qnames[tag][0] if tag is None: if text: write(_escape_cdata(text)) @@ -983,11 +986,11 @@ def _serialize_html(write, elem, qnames, namespaces, **kwargs): if isinstance(k, QName): k = k.text if isinstance(v, QName): - v = qnames[v.text] + v = qnames[v.text][0] else: v = _escape_attrib_html(v) # FIXME: handle boolean attributes - write(" %s=\"%s\"" % (qnames[k], v)) + write(" %s=\"%s\"" % (qnames[k][1], v)) write(">") ltag = tag.lower() if text: From 11c924a964a6ca9350a30e1d0a88165058ffb40f Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Sun, 9 Dec 2018 11:20:35 +0100 Subject: [PATCH 06/12] Only fill tag/attribute part of qnames cache when it is actually used Currently there is no real need to do this, since the serialization used for tags and attributes is identical. But in the future, they will use different serializations and we want to avoid defining namespace prefixes for namespaces that are not going to appear in the serialization. --- Lib/xml/etree/ElementTree.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 9bb5458a6362168..2e35df817582c71 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -870,10 +870,16 @@ def serialize_qname(qname): ) return qname - def add_qname(qname): - if qname not in qnames: - serialized = serialize_qname(qname) - qnames[qname] = (serialized, serialized) + def add_qname(qname, is_attr=False): + ser_tag, ser_attr = qnames.get(qname, (None, None)) + if is_attr: + if ser_attr is None: + ser_attr = serialize_qname(qname) + qnames[qname] = (ser_tag, ser_attr) + else: + if ser_tag is None: + ser_tag = serialize_qname(qname) + qnames[qname] = (ser_tag, ser_attr) # populate qname and namespaces table for elem in elem.iter(): @@ -889,7 +895,7 @@ def add_qname(qname): key = key.text elif not isinstance(key, str): _raise_serialization_error(key) - add_qname(key) + add_qname(key, is_attr=True) if isinstance(value, QName): add_qname(value.text) text = elem.text From 4a07bca2453c78c0812e97f81018a9ce689071dc Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Sun, 9 Dec 2018 09:52:32 +0100 Subject: [PATCH 07/12] Add default namespace to mapping after filling the qnames cache This is in preparation of a future change that requires the default namespace to not be applied to attributes. Because the default namespace is no longer in the mapping, the first generated synthetic namespace prefix is now always "ns0", while previously it was "ns0" or "ns1" depending on whether a default namespace was provided. I updated the expected serialization in one test case to match this new behavior. --- Lib/test/test_xml_etree.py | 4 ++-- Lib/xml/etree/ElementTree.py | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 7e8585fcae0f377..c7eeb893d934fa9 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -1619,9 +1619,9 @@ def test_bug_200709_default_namespace(self): s = ET.SubElement(e, "{default}elem") s = ET.SubElement(e, "{not-default}elem") self.assertEqual(serialize(e, default_namespace="default"), # 2 - '' + '' '' - '' + '' '') e = ET.Element("{default}elem") diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 2e35df817582c71..daf6ef57e1111fc 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -843,20 +843,21 @@ def _namespaces(elem, default_namespace=None): # maps uri:s to prefixes namespaces = {} - if default_namespace: - namespaces[default_namespace] = "" def serialize_qname(qname): # calculate serialized qname representation if qname[:1] == "{": uri, local = qname[1:].rsplit("}", 1) - prefix = namespaces.get(uri) - if prefix is None: - prefix = _namespace_map.get(uri) + if uri == default_namespace: + prefix = "" + else: + prefix = namespaces.get(uri) if prefix is None: - prefix = "ns%d" % len(namespaces) - if prefix != "xml": - namespaces[uri] = prefix + prefix = _namespace_map.get(uri) + if prefix is None: + prefix = "ns%d" % len(namespaces) + if prefix != "xml": + namespaces[uri] = prefix if prefix: return "%s:%s" % (prefix, local) else: @@ -901,6 +902,9 @@ def add_qname(qname, is_attr=False): text = elem.text if isinstance(text, QName): add_qname(text.text) + + if default_namespace: + namespaces[default_namespace] = "" return qnames, namespaces def _serialize_xml(write, elem, qnames, namespaces, From 4cf93e182b04601575ca2bff1d8cb1bd53901162 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Sun, 9 Dec 2018 13:53:03 +0100 Subject: [PATCH 08/12] Have _namespaces() return a mapping from prefix to namespace Previously, it returned a mapping from namespace to prefix. But later the serialization code sorted that mapping by prefix, so inverting the mapping simplifies things. Another reason to invert the mapping is that currently there is a 1:1 correspondence between prefixes and namespaces, but in the future it will be possible for the default namespace to exist both with an empty prefix and with a actual prefix. --- Lib/xml/etree/ElementTree.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index daf6ef57e1111fc..6f83869d97a567a 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -903,9 +903,10 @@ def add_qname(qname, is_attr=False): if isinstance(text, QName): add_qname(text.text) + prefix_map = {prefix: ns for ns, prefix in namespaces.items()} if default_namespace: - namespaces[default_namespace] = "" - return qnames, namespaces + prefix_map[""] = default_namespace + return qnames, prefix_map def _serialize_xml(write, elem, qnames, namespaces, short_empty_elements, **kwargs): @@ -928,8 +929,7 @@ def _serialize_xml(write, elem, qnames, namespaces, items = list(elem.items()) if items or namespaces: if namespaces: - for v, k in sorted(namespaces.items(), - key=lambda x: x[1]): # sort on prefix + for k, v in sorted(namespaces.items()): if k: k = ":" + k write(" xmlns%s=\"%s\"" % ( @@ -984,8 +984,7 @@ def _serialize_html(write, elem, qnames, namespaces, **kwargs): items = list(elem.items()) if items or namespaces: if namespaces: - for v, k in sorted(namespaces.items(), - key=lambda x: x[1]): # sort on prefix + for k, v in sorted(namespaces.items()): if k: k = ":" + k write(" xmlns%s=\"%s\"" % ( From 6bf5eab018e3de62c38bc22024c7445c40c4fe10 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Sat, 8 Dec 2018 16:30:13 +0100 Subject: [PATCH 09/12] Add ElementTree test for unqualified attribute names This is not my work: I extracted this test case from the second iteration of wiml's patch for issue 17088. https://bugs.python.org/file33125/bug17088_2.patch I did modify the line wrapping, as the original patch went very wide (over column 100). I also updated the prefix numbering in the expected serialized output, since numbering always starts at 0 now. --- Lib/test/test_xml_etree.py | 75 +++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index c7eeb893d934fa9..e5a60ef80dc3e60 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -173,7 +173,7 @@ def assertEqualElements(self, alice, bob): # -------------------------------------------------------------------- # element tree tests -class ElementTreeTest(unittest.TestCase): +class ElementTreeTest(ElementTestCase, unittest.TestCase): def serialize_check(self, elem, expected): self.assertEqual(serialize(elem), expected) @@ -972,6 +972,79 @@ def test_qname(self): self.assertNotEqual(q1, 'ns:tag') self.assertEqual(q1, '{ns}tag') + def test_namespace_attribs(self): + # Unprefixed attributes are unqualified even if a default + # namespace is in effect. (This is a little unclear in some + # versions of the XML TR but is clarified in errata and other + # versions.) See bugs.python.org issue 17088. + # + # The reasoning behind this, alluded to in the spec, is that + # attribute meanings already depend on the element they're + # attached to; attributes have always lived in per-element + # namespaces even before explicit XML namespaces were + # introduced. For that reason qualified attribute names are + # only really needed when one XML module defines attributes + # that can be placed on elements defined in a different module + # (such as happens with XLINK or, for that matter, the XML + # namespace spec itself). + e = ET.XML( + '' + '' + '' + '' + '') + self.assertEqual(e.tag, '{space1}elt') + self.assertEqual(e.get('foo'), 'value') + self.assertIsNone(e.get('{space1}foo')) + self.assertIsNone(e.get('{space2}foo')) + self.assertEqual(e[0].tag, '{space1}foo') + self.assertEqual(e[0].attrib, { 'foo': 'value2', + '{space2}foo': 'value3' }) + self.assertEqual(e[1].tag, '{space2}foo') + self.assertEqual(e[1].attrib, { 'foo': 'value4', + '{space1}foo': 'value5', + '{space2}foo': 'value6' }) + self.assertEqual(e[2].tag, 'foo') + self.assertEqual(e[2].attrib, { 'foo': 'value7', + '{space1}foo': 'value8' }) + + serialized1 = ( + '' + '' + '' + '' + '') + self.assertEqual(serialize(e), serialized1) + self.assertEqualElements(e, ET.XML(serialized1)) + + # Test writing with a default namespace. + with self.assertRaisesRegex(ValueError, + 'cannot use non-qualified name.* with default_namespace option'): + serialize(e, default_namespace="space1") + + # Remove the unqualified element from the tree so we can test + # further. + del e[2] + + # Serialization can require a namespace prefix to be declared for + # space1 even if no elements use that prefix, in order to + # write an attribute name in that namespace. + serialized2 = ( + '' + '' + '' + '') + self.assertEqual(serialize(e, default_namespace="space2"), serialized2) + self.assertEqualElements(e, ET.XML(serialized2)) + + serialized3 = ( + '' + '' + '' + '') + self.assertEqual(serialize(e, default_namespace="space1"), serialized3) + self.assertEqualElements(e, ET.XML(serialized3)) + def test_doctype_public(self): # Test PUBLIC doctype. From d80197ecd9c2e0f9bdecdf07e1e83689d5aab717 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Sun, 9 Dec 2018 12:02:43 +0100 Subject: [PATCH 10/12] Do not apply default namespace to attribute names Unprefixed attributes are considered to not be in any namespace, according to the XML namespaces spec. This also fixes issue 17088, since the exception that rejects non-qualified names when using the default_namespace option is no longer raised when serializing an attribute name. --- Lib/xml/etree/ElementTree.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 6f83869d97a567a..dfa7f5ea05982ac 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -844,11 +844,11 @@ def _namespaces(elem, default_namespace=None): # maps uri:s to prefixes namespaces = {} - def serialize_qname(qname): + def serialize_qname(qname, is_attr): # calculate serialized qname representation if qname[:1] == "{": uri, local = qname[1:].rsplit("}", 1) - if uri == default_namespace: + if not is_attr and uri == default_namespace: prefix = "" else: prefix = namespaces.get(uri) @@ -863,10 +863,10 @@ def serialize_qname(qname): else: return local # default element else: - if default_namespace: + if not is_attr and default_namespace: # FIXME: can this be handled in XML 1.0? raise ValueError( - "cannot use non-qualified name (%s) with " + "cannot use non-qualified name (<%s>) with " "default_namespace option" % qname ) return qname @@ -875,11 +875,11 @@ def add_qname(qname, is_attr=False): ser_tag, ser_attr = qnames.get(qname, (None, None)) if is_attr: if ser_attr is None: - ser_attr = serialize_qname(qname) + ser_attr = serialize_qname(qname, True) qnames[qname] = (ser_tag, ser_attr) else: if ser_tag is None: - ser_tag = serialize_qname(qname) + ser_tag = serialize_qname(qname, False) qnames[qname] = (ser_tag, ser_attr) # populate qname and namespaces table From 31c32a3fa57f4d1da39b08527220f08b7f9342d9 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Sun, 9 Dec 2018 14:14:06 +0100 Subject: [PATCH 11/12] Avoid serializing qname twice when no default_namespace is used When no default_namespace is passed, serialize_qname() returns the same value regardless of whether is_attr is True or False. So we can save some time by storing the serialization for both the tag and attribute in the cache when one of them is computed. --- Lib/xml/etree/ElementTree.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index dfa7f5ea05982ac..0683020352de974 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -876,10 +876,14 @@ def add_qname(qname, is_attr=False): if is_attr: if ser_attr is None: ser_attr = serialize_qname(qname, True) + if not default_namespace: + ser_tag = ser_attr qnames[qname] = (ser_tag, ser_attr) else: if ser_tag is None: ser_tag = serialize_qname(qname, False) + if not default_namespace: + ser_attr = ser_tag qnames[qname] = (ser_tag, ser_attr) # populate qname and namespaces table From 52c4b8e07fede2c3eba354a2e71ccc0b51822d08 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Sun, 9 Dec 2018 15:00:16 +0100 Subject: [PATCH 12/12] Add NEWS.d item for ElementTree attribute serialization with default namespace --- .../next/Library/2018-12-09-14-58-08.bpo-17088.AHFvrn.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2018-12-09-14-58-08.bpo-17088.AHFvrn.rst diff --git a/Misc/NEWS.d/next/Library/2018-12-09-14-58-08.bpo-17088.AHFvrn.rst b/Misc/NEWS.d/next/Library/2018-12-09-14-58-08.bpo-17088.AHFvrn.rst new file mode 100644 index 000000000000000..e92d690d4331fd7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-09-14-58-08.bpo-17088.AHFvrn.rst @@ -0,0 +1,5 @@ +ElementTree's serialization of attribute names when a default namespace is +passed was corrected. Previously, unqualified attribute names would be +rejected unnecessarily, while attributes in the default namespace would have +their prefix stripped, which goes against the rules for namespace defaulting +and uniqueness of attributes as specified in the XML namespaces spec.