Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Doc/library/xml.etree.elementtree.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1179,7 +1179,9 @@ ElementTree Objects
*xml_declaration* controls if an XML declaration should be added to the
file. Use ``False`` for never, ``True`` for always, ``None``
for only if not US-ASCII or UTF-8 or Unicode (default is ``None``).
*default_namespace* sets the default XML namespace (for "xmlns").
*default_namespace* sets the default XML namespace (for "xmlns"). This
option takes precedence over a default namespace registered using
:func:`register_namespace`.
*method* is either ``"xml"``, ``"html"`` or ``"text"`` (default is
``"xml"``).
The keyword-only *short_empty_elements* parameter controls the formatting
Expand Down
64 changes: 64 additions & 0 deletions Lib/test/test_xml_etree.py
Original file line number Diff line number Diff line change
Expand Up @@ -2074,6 +2074,14 @@ def test_xinclude_failures(self):

class BugsTest(unittest.TestCase):

@classmethod
def setUpClass(cls):
cls._orig_namespace_map = ET.register_namespace._namespace_map.copy()

def tearDown(self):
ET.register_namespace._namespace_map.clear()
ET.register_namespace._namespace_map.update(self._orig_namespace_map)

def test_bug_xmltoolkit21(self):
# marshaller gives obscure errors for non-string values

Expand Down Expand Up @@ -2272,6 +2280,62 @@ def test_bug_200709_register_namespace(self):
self.assertEqual(ET.tostring(e),
b'<dc:title xmlns:dc="http://purl.org/dc/elements/1.1/" />')

def test_pre_gh118416_register_default_namespace_behaviour(self):
# All these examples worked prior to fixing bug gh118416
ET.register_namespace("", "gh118416")

# If the registered default prefix's URI is not used, don't raise an
# error
e = ET.Element("{other}elem")
self.assertEqual(
serialize(e),
'<ns0:elem xmlns:ns0="other" />',
)
# no error even with unqualified tags
e = ET.Element("elem")
self.assertEqual(
serialize(e),
'<elem />',
)

# Uses registered default prefix, if used in tree
e = ET.Element("{gh118416}elem")
self.assertEqual(
serialize(e),
'<elem xmlns="gh118416" />',
)

# Use explicitly provided default_namespace if registered default
# prefix is not used.
e = ET.Element("{default}elem")
ET.SubElement(e, "{other}otherEl")
self.assertEqual(
serialize(e, default_namespace="default"),
(
'<elem xmlns="default" xmlns:ns1="other">'
'<ns1:otherEl />'
'</elem>'
),
)

def test_gh118416_register_default_namespace(self):
ET.register_namespace("", "gh118416")
e = ET.Element("{gh118416}elem")
ET.SubElement(e, "noPrefixElem")
with self.assertRaises(ValueError) as cm:
serialize(e)
self.assertEqual(
str(cm.exception),
"cannot use non-qualified names with default_namespace option",
)

e = ET.Element("{gh118416}elem")
# explicitly set default_namespace takes precedence
self.assertEqual(
serialize(e, default_namespace="otherdefault"),
'<ns1:elem xmlns="otherdefault" xmlns:ns1="gh118416" />',
)

def test_bug_200709_element_comment(self):
# Not sure if this can be fixed, really (since the serializer needs
# ET.Comment, not cET.comment).
Expand Down
54 changes: 44 additions & 10 deletions Lib/xml/etree/ElementTree.py
Original file line number Diff line number Diff line change
Expand Up @@ -724,9 +724,13 @@ def write(self, file_or_filename,
if method == "text":
_serialize_text(write, self._root)
else:
qnames, namespaces = _namespaces(self._root, default_namespace)
qnames, namespaces_to_declare = _namespaces(self._root, default_namespace)
if default_namespace:
# Always write the default_namespace if provided, even if
# it wasn't referenced in the xml
namespaces_to_declare[default_namespace] = ""
serialize = _serialize[method]
serialize(write, self._root, qnames, namespaces,
serialize(write, self._root, qnames, namespaces_to_declare,
short_empty_elements=short_empty_elements)

def write_c14n(self, file):
Expand Down Expand Up @@ -792,21 +796,41 @@ def _namespaces(elem, default_namespace=None):
# maps qnames to *encoded* prefix:local names
qnames = {None: None}

global_default_uri = None
for uri, prefix in list(_namespace_map.items()):
if prefix == "":
global_default_uri = uri
# register_namespaces() only allows a unique set of prefixes so ""
# will only occur once
break

# maps uri:s to prefixes
namespaces = {}
if default_namespace:
namespaces[default_namespace] = ""
namespace_map = _namespace_map.copy()
if global_default_uri:
del namespace_map[global_default_uri]
namespace_map[default_namespace] = ""
else:
namespace_map = _namespace_map

def add_qname(qname):
# calculate serialized qname representation
found_unqualified = False
try:
if qname[:1] == "{":
uri, tag = qname[1:].rsplit("}", 1)
prefix = namespaces.get(uri)
if prefix is None:
prefix = _namespace_map.get(uri)
prefix = namespace_map.get(uri)
if prefix is None:
prefix = "ns%d" % len(namespaces)
cnt = len(namespaces)
# Keep the pre-gh118416 numbering scheme where the
# default namespace was always considered the first
# namespace
if default_namespace and default_namespace not in namespaces:
cnt += 1
prefix = "ns%d" % cnt
if prefix != "xml":
namespaces[uri] = prefix
if prefix:
Expand All @@ -820,31 +844,41 @@ def add_qname(qname):
"cannot use non-qualified names with "
"default_namespace option"
)
found_unqualified = True
qnames[qname] = qname
except TypeError:
_raise_serialization_error(qname)
return found_unqualified

# populate qname and namespaces table
found_unqualified = False
for elem in elem.iter():
tag = elem.tag
if isinstance(tag, QName):
if tag.text not in qnames:
add_qname(tag.text)
found_unqualified |= add_qname(tag.text)
elif isinstance(tag, str):
if tag not in qnames:
add_qname(tag)
found_unqualified |= 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():
if isinstance(key, QName):
key = key.text
if key not in qnames:
add_qname(key)
found_unqualified |= add_qname(key)
if isinstance(value, QName) and value.text not in qnames:
add_qname(value.text)
found_unqualified |= add_qname(value.text)
text = elem.text
if isinstance(text, QName) and text.text not in qnames:
add_qname(text.text)
found_unqualified |= add_qname(text.text)

if found_unqualified and global_default_uri in namespaces:
# A deferred check of using unqualified tags when a global default
# namespace is registered
raise ValueError(
"cannot use non-qualified names with default_namespace option"
)
return qnames, namespaces

def _serialize_xml(write, elem, qnames, namespaces,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix possible issues in :mod:`xml.etree.ElementTree` when serialising xml if
a default namespace prefix (``""``) has been registered with
:func:`xml.etree.ElementTree.register_namespace()`