@@ -379,6 +379,109 @@ def _write_data(writer, text, attr):
379379 text = text .replace ("\t " , "	" )
380380 writer .write (text )
381381
382+
383+ # The "xml" prefix is bound by definition and is never declared.
384+ _ROOT_NSMAP = {"xml" : XML_NAMESPACE }
385+
386+
387+ def _bind_namespace (nsmap , inherited , prefix , uri ):
388+ """Bind *prefix* in *nsmap*, copying it if it is still the inherited one."""
389+ if nsmap is inherited :
390+ nsmap = dict (inherited )
391+ nsmap [prefix ] = uri
392+ return nsmap
393+
394+
395+ def _in_scope_namespaces (element ):
396+ """Return the namespaces in scope for *element*, as written by writexml."""
397+ ancestors = []
398+ node = element .parentNode
399+ while node is not None and node .nodeType == Node .ELEMENT_NODE :
400+ ancestors .append (node )
401+ node = node .parentNode
402+ nsmap = _ROOT_NSMAP
403+ for node in reversed (ancestors ):
404+ nsmap , _ = _fixup_namespaces (node , nsmap )
405+ return nsmap
406+
407+
408+ def _fixup_namespaces (element , nsmap ):
409+ """Compute namespace declarations missing for the serialized element.
410+
411+ *nsmap* is the mapping of prefixes to namespace URIs in scope for the
412+ element. Return the mapping in scope for its children and the list of
413+ (name, value) pairs of the attributes to be written, starting with the
414+ added namespace declarations. The element and its attributes are not
415+ modified.
416+ """
417+ attrs = element ._attrs
418+ uri = element .namespaceURI
419+ if not attrs and not uri and not nsmap .get (None ):
420+ # Neither the element nor its attributes need a declaration.
421+ return nsmap , ()
422+
423+ inherited = nsmap
424+ declarations = []
425+ # (name, value, namespace URI, attribute) of the attributes to write.
426+ entries = []
427+ if attrs :
428+ for attr in attrs .values ():
429+ name = attr .name
430+ attr_uri = attr .namespaceURI
431+ if (attr_uri == XMLNS_NAMESPACE or name == "xmlns"
432+ or name .startswith ("xmlns:" )):
433+ # Declarations already present in the document take precedence.
434+ nsmap = _bind_namespace (
435+ nsmap , inherited ,
436+ attr .localName if attr .prefix else None , attr .value )
437+ attr_uri = None
438+ elif attr_uri == XML_NAMESPACE :
439+ # The xml prefix is bound by definition.
440+ attr_uri = None
441+ entries .append ((name , attr .value , attr_uri , attr ))
442+
443+ if uri :
444+ prefix , _ , _ = element .tagName .rpartition (':' )
445+ prefix = prefix or None
446+ if nsmap .get (prefix ) != uri :
447+ nsmap = _bind_namespace (nsmap , inherited , prefix , uri )
448+ declarations .append (("xmlns:" + prefix if prefix else "xmlns" , uri ))
449+ elif nsmap .get (None ) and ':' not in element .tagName :
450+ # The element is in no namespace, undeclare the default one.
451+ nsmap = _bind_namespace (nsmap , inherited , None , None )
452+ declarations .append (("xmlns" , "" ))
453+
454+ items = []
455+ prefixes = None # namespace URI -> prefix, built only when needed
456+ n = 0
457+ for name , value , attr_uri , attr in entries :
458+ if attr_uri is not None :
459+ # Unprefixed attributes are in no namespace, so an attribute
460+ # in a namespace always needs a prefix.
461+ prefix , _ , _ = name .rpartition (':' )
462+ if not prefix :
463+ # Reuse a prefix bound to the namespace, or invent one.
464+ if prefixes is None :
465+ prefixes = {u : p for p , u in nsmap .items ()
466+ if p is not None }
467+ prefix = prefixes .get (attr_uri )
468+ if prefix is None :
469+ while nsmap .get ("ns%d" % n ) is not None :
470+ n += 1
471+ prefix = "ns%d" % n
472+ name = "%s:%s" % (prefix , attr .localName )
473+ if nsmap .get (prefix ) != attr_uri :
474+ nsmap = _bind_namespace (nsmap , inherited , prefix , attr_uri )
475+ declarations .append (("xmlns:" + prefix , attr_uri ))
476+ if prefixes is not None :
477+ prefixes [attr_uri ] = prefix
478+ items .append ((name , value ))
479+
480+ if declarations :
481+ return nsmap , declarations + items
482+ return nsmap , items
483+
484+
382485def _get_elements_by_tagName_helper (parent , name , rc ):
383486 for node in parent .childNodes :
384487 if node .nodeType == Node .ELEMENT_NODE and \
@@ -944,7 +1047,8 @@ def getElementsByTagNameNS(self, namespaceURI, localName):
9441047 def __repr__ (self ):
9451048 return "<DOM Element: %s at %#x>" % (self .tagName , id (self ))
9461049
947- def writexml (self , writer , indent = "" , addindent = "" , newl = "" ):
1050+ def writexml (self , writer , indent = "" , addindent = "" , newl = "" , * ,
1051+ _nsmap = None ):
9481052 """Write an XML element to a file-like object
9491053
9501054 Write the element to the writer object that must provide
@@ -953,13 +1057,14 @@ def writexml(self, writer, indent="", addindent="", newl=""):
9531057 # indent = current indentation
9541058 # addindent = indentation to add to higher levels
9551059 # newl = newline string
1060+ if _nsmap is None :
1061+ _nsmap = _in_scope_namespaces (self )
9561062 writer .write (indent + "<" + self .tagName )
9571063
958- attrs = self ._get_attributes ()
959-
960- for a_name in attrs .keys ():
1064+ nsmap , items = _fixup_namespaces (self , _nsmap )
1065+ for a_name , value in items :
9611066 writer .write (" %s=\" " % a_name )
962- _write_data (writer , attrs [ a_name ]. value , True )
1067+ _write_data (writer , value , True )
9631068 writer .write ("\" " )
9641069 if self .childNodes :
9651070 writer .write (">" )
@@ -974,7 +1079,15 @@ def writexml(self, writer, indent="", addindent="", newl=""):
9741079 else :
9751080 writer .write (newl )
9761081 for node in self .childNodes :
977- node .writexml (writer , indent + addindent , addindent , newl )
1082+ if type (node ).writexml is Element .writexml :
1083+ # Pass the namespaces in scope to the standard
1084+ # implementation; an overridden writexml() has the
1085+ # documented signature and computes them itself.
1086+ node .writexml (writer , indent + addindent , addindent ,
1087+ newl , _nsmap = nsmap )
1088+ else :
1089+ node .writexml (writer , indent + addindent , addindent ,
1090+ newl )
9781091 writer .write (indent )
9791092 writer .write ("</%s>%s" % (self .tagName , newl ))
9801093 else :
0 commit comments