Skip to content

Commit 2963b99

Browse files
Reuse a prefix in scope for an unprefixed attribute in a namespace
A prefix is only invented if no prefix in scope is bound to the namespace of the attribute. The reverse mapping is built only when such attribute is encountered, so there is no cost in the common case. The counter of invented prefixes is not reset for every attribute.
1 parent 6a76a10 commit 2963b99

2 files changed

Lines changed: 51 additions & 4 deletions

File tree

Lib/test/test_minidom.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,44 @@ def testWriteXMLAttributeNamespacePrefix(self):
597597
'<root xmlns:ns0="http://xml.python.org/ns" '
598598
'xmlns:ns1="http://xml.python.org/ns2" '
599599
'ns0:attr="value" ns1:attr2="value2"/>')
600+
# The same namespace gets the same prefix.
601+
root.setAttributeNS("http://xml.python.org/ns", "attr3", "value3")
602+
self.assertEqual(dom.documentElement.toxml(),
603+
'<root xmlns:ns0="http://xml.python.org/ns" '
604+
'xmlns:ns1="http://xml.python.org/ns2" '
605+
'ns0:attr="value" ns1:attr2="value2" ns0:attr3="value3"/>')
606+
dom.unlink()
607+
608+
def testWriteXMLAttributeNamespacePrefixReused(self):
609+
# A prefix already bound to the namespace of the attribute is used.
610+
dom = Document()
611+
root = dom.appendChild(
612+
dom.createElementNS("http://xml.python.org/ns", "p:root"))
613+
root.setAttributeNS("http://xml.python.org/ns", "attr", "value")
614+
self.assertEqual(dom.documentElement.toxml(),
615+
'<p:root xmlns:p="http://xml.python.org/ns" p:attr="value"/>')
616+
# The prefix can be bound for an ancestor.
617+
child = root.appendChild(dom.createElement("child"))
618+
child.setAttributeNS("http://xml.python.org/ns", "attr", "value")
619+
self.assertEqual(child.toxml(), '<child p:attr="value"/>')
620+
# The prefix bound for a preceding attribute is reused.
621+
root.setAttributeNS("http://xml.python.org/ns3", "q:attr3", "value3")
622+
root.setAttributeNS("http://xml.python.org/ns3", "attr4", "value4")
623+
self.assertEqual(dom.documentElement.toxml(),
624+
'<p:root xmlns:p="http://xml.python.org/ns" '
625+
'xmlns:q="http://xml.python.org/ns3" '
626+
'p:attr="value" q:attr3="value3" q:attr4="value4">'
627+
'<child p:attr="value"/></p:root>')
628+
root.removeAttributeNS("http://xml.python.org/ns3", "attr3")
629+
root.removeAttributeNS("http://xml.python.org/ns3", "attr4")
630+
# The prefix must not be taken by an explicit declaration.
631+
root.setAttributeNS(xml.dom.XMLNS_NAMESPACE, "xmlns:ns0", "other")
632+
root.setAttributeNS("http://xml.python.org/ns2", "attr2", "value2")
633+
self.assertEqual(dom.documentElement.toxml(),
634+
'<p:root xmlns:p="http://xml.python.org/ns" '
635+
'xmlns:ns1="http://xml.python.org/ns2" '
636+
'p:attr="value" xmlns:ns0="other" ns1:attr2="value2">'
637+
'<child p:attr="value"/></p:root>')
600638
dom.unlink()
601639

602640
def testWriteXMLXMLPrefix(self):

Lib/xml/dom/minidom.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -420,20 +420,29 @@ def _fixup_namespaces(element, nsmap):
420420
declarations.append(("xmlns", ""))
421421

422422
items = []
423+
prefixes = None # namespace URI -> prefix, built only when needed
424+
n = 0
423425
for name, value, attr_uri, attr in entries:
424426
if attr_uri is not None:
425427
# Unprefixed attributes are in no namespace, so an attribute
426428
# in a namespace always needs a prefix.
427429
prefix, _, _ = name.rpartition(':')
428430
if not prefix:
429-
n = 0
430-
while nsmap.get("ns%d" % n) is not None:
431-
n += 1
432-
prefix = "ns%d" % n
431+
# Reuse a prefix bound to the namespace, or invent one.
432+
if prefixes is None:
433+
prefixes = {u: p for p, u in nsmap.items()
434+
if p is not None}
435+
prefix = prefixes.get(attr_uri)
436+
if prefix is None:
437+
while nsmap.get("ns%d" % n) is not None:
438+
n += 1
439+
prefix = "ns%d" % n
433440
name = "%s:%s" % (prefix, attr.localName)
434441
if nsmap.get(prefix) != attr_uri:
435442
nsmap = _bind_namespace(nsmap, inherited, prefix, attr_uri)
436443
declarations.append(("xmlns:" + prefix, attr_uri))
444+
if prefixes is not None:
445+
prefixes[attr_uri] = prefix
437446
items.append((name, value))
438447

439448
if declarations:

0 commit comments

Comments
 (0)