Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(10592)

Unified Diff: Lib/xml/etree/ElementTree.py

Issue 17088: ElementTree incorrectly refuses to write attributes without namespaces when default_namespace is used
Patch Set: Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « Lib/test/test_xml_etree.py ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
--- a/Lib/xml/etree/ElementTree.py Fri Dec 13 17:21:42 2013 -0500
+++ b/Lib/xml/etree/ElementTree.py Fri Dec 13 15:15:02 2013 -0800
@@ -772,9 +772,9 @@
if method == "text":
_serialize_text(write, self._root)
else:
- qnames, namespaces = _namespaces(self._root, default_namespace)
+ elt_qnames, attr_qnames, namespaces = _namespaces(self._root, default_namespace)
serialize = _serialize[method]
- serialize(write, self._root, qnames, namespaces,
+ serialize(write, self._root, elt_qnames, attr_qnames, namespaces,
short_empty_elements=short_empty_elements)
def write_c14n(self, file):
@@ -837,39 +837,58 @@
yield file.write
def _namespaces(elem, default_namespace=None):
- # identify namespaces used in this tree
+ # identify namespaces used in this tree, assign namespace prefixes
+ # as needed, and create cache dicts which map element and attribute
+ # names to their serialized representations
# maps qnames to *encoded* prefix:local names
- qnames = {None: None}
+ # elts and attrs may need distinct mappings because default
+ # namespaces affect them differently
+ elt_qnames = {None: None}
+ if default_namespace is None:
+ attr_qnames = elt_qnames
+ else:
+ attr_qnames = {None: None}
# maps uri:s to prefixes
namespaces = {}
- if default_namespace:
- namespaces[default_namespace] = ""
- def add_qname(qname):
+ # this offset is just here to make our generated namespace
+ # prefixes predictable to the unit tests
+ ns0 = 1 if default_namespace else 0
+
+ def add_qname(qname, defaultable):
# calculate serialized qname representation
+ qnames = elt_qnames if defaultable else attr_qnames
try:
if qname[:1] == "{":
uri, tag = qname[1:].rsplit("}", 1)
+ if defaultable and uri == default_namespace:
+ qnames[qname] = tag # Default namespace, no prefix
+ return
+
prefix = namespaces.get(uri)
if prefix is None:
+ # Assign a namespace prefix
prefix = _namespace_map.get(uri)
if prefix is None:
- prefix = "ns%d" % len(namespaces)
+ prefix = "ns%d" % (ns0 + len(namespaces))
if prefix != "xml":
namespaces[uri] = prefix
- if prefix:
- qnames[qname] = "%s:%s" % (prefix, tag)
- else:
- qnames[qname] = tag # default element
+ qnames[qname] = "%s:%s" % (prefix, tag)
else:
- if default_namespace:
- # FIXME: can this be handled in XML 1.0?
+ if defaultable and default_namespace:
+ # A default namespace can be undeclared
+ # (see http://www.w3.org/TR/REC-xml-names/#defaulting)
+ # but only by placing an xmlns="" attribute on the
+ # element and possibly re-declaring the default
+ # namespace for child elements. Our serializers
+ # can't do that. FIXME.
raise ValueError(
- "cannot use non-qualified names with "
- "default_namespace option"
+ "cannot use non-qualified names (<%s>) with "
+ "default_namespace option" % (qname,)
)
+ # Unqualified name -> unprefixed serialized name
qnames[qname] = qname
except TypeError:
_raise_serialization_error(qname)
@@ -878,26 +897,36 @@
for elem in elem.iter():
tag = elem.tag
if isinstance(tag, QName):
- if tag.text not in qnames:
- add_qname(tag.text)
+ if tag.text not in elt_qnames:
+ add_qname(tag.text, True)
elif isinstance(tag, str):
- if tag not in qnames:
- add_qname(tag)
+ if tag not in elt_qnames:
+ add_qname(tag, True)
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)
- if isinstance(value, QName) and value.text not in qnames:
- add_qname(value.text)
+ if key not in attr_qnames:
+ add_qname(key, False)
+ if isinstance(value, QName) and value.text not in attr_qnames:
+ # FIXME: Should default ns be applied to attrib *values*?
+ # Opting for correctness here even if it results in an
+ # unneeded namespace prefix sometimes.
+ add_qname(value.text, False)
text = elem.text
- if isinstance(text, QName) and text.text not in qnames:
- add_qname(text.text)
- return qnames, namespaces
+ if isinstance(text, QName) and text.text not in elt_qnames:
+ add_qname(text.text, True)
+
+ if default_namespace:
+ prefixes_list = [ (default_namespace, "") ]
+ prefixes_list.extend(namespaces.items())
+ else:
+ prefixes_list = namespaces.items()
+
+ return elt_qnames, attr_qnames, prefixes_list
-def _serialize_xml(write, elem, qnames, namespaces,
+def _serialize_xml(write, elem, elt_qnames, attr_qnames, namespaces,
short_empty_elements, **kwargs):
tag = elem.tag
text = elem.text
@@ -906,19 +935,19 @@
elif tag is ProcessingInstruction:
write("<?%s?>" % text)
else:
- tag = qnames[tag]
+ tag = elt_qnames[tag]
if tag is None:
if text:
write(_escape_cdata(text))
for e in elem:
- _serialize_xml(write, e, qnames, None,
+ _serialize_xml(write, e, elt_qnames, attr_qnames, None,
short_empty_elements=short_empty_elements)
else:
write("<" + tag)
items = list(elem.items())
if items or namespaces:
if namespaces:
- for v, k in sorted(namespaces.items(),
+ for v, k in sorted(namespaces,
key=lambda x: x[1]): # sort on prefix
if k:
k = ":" + k
@@ -930,16 +959,16 @@
if isinstance(k, QName):
k = k.text
if isinstance(v, QName):
- v = qnames[v.text]
+ v = attr_qnames[v.text]
else:
v = _escape_attrib(v)
- write(" %s=\"%s\"" % (qnames[k], v))
+ write(" %s=\"%s\"" % (attr_qnames[k], v))
if text or len(elem) or not short_empty_elements:
write(">")
if text:
write(_escape_cdata(text))
for e in elem:
- _serialize_xml(write, e, qnames, None,
+ _serialize_xml(write, e, elt_qnames, attr_qnames, None,
short_empty_elements=short_empty_elements)
write("</" + tag + ">")
else:
@@ -955,7 +984,7 @@
except NameError:
pass
-def _serialize_html(write, elem, qnames, namespaces, **kwargs):
+def _serialize_html(write, elem, elt_qnames, attr_qnames, namespaces, **kwargs):
tag = elem.tag
text = elem.text
if tag is Comment:
@@ -963,18 +992,18 @@
elif tag is ProcessingInstruction:
write("<?%s?>" % _escape_cdata(text))
else:
- tag = qnames[tag]
+ tag = elt_qnames[tag]
if tag is None:
if text:
write(_escape_cdata(text))
for e in elem:
- _serialize_html(write, e, qnames, None)
+ _serialize_html(write, e, elt_qnames, attr_qnames, None)
else:
write("<" + tag)
items = list(elem.items())
if items or namespaces:
if namespaces:
- for v, k in sorted(namespaces.items(),
+ for v, k in sorted(namespaces,
key=lambda x: x[1]): # sort on prefix
if k:
k = ":" + k
@@ -986,11 +1015,11 @@
if isinstance(k, QName):
k = k.text
if isinstance(v, QName):
- v = qnames[v.text]
+ v = attr_qnames[v.text]
else:
v = _escape_attrib_html(v)
# FIXME: handle boolean attributes
- write(" %s=\"%s\"" % (qnames[k], v))
+ write(" %s=\"%s\"" % (attr_qnames[k], v))
write(">")
ltag = tag.lower()
if text:
@@ -999,7 +1028,7 @@
else:
write(_escape_cdata(text))
for e in elem:
- _serialize_html(write, e, qnames, None)
+ _serialize_html(write, e, elt_qnames, attr_qnames, None)
if ltag not in HTML_EMPTY:
write("</" + tag + ">")
if elem.tail:
« no previous file with comments | « Lib/test/test_xml_etree.py ('k') | no next file » | no next file with comments »

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+