@@ -681,7 +681,8 @@ def write(self, file_or_filename,
681681 xml_declaration = None ,
682682 default_namespace = None ,
683683 method = None , * ,
684- short_empty_elements = True ):
684+ short_empty_elements = True ,
685+ nsmap = None ):
685686 """Write element tree to a file as XML.
686687
687688 Arguments:
@@ -696,7 +697,7 @@ def write(self, file_or_filename,
696697
697698 *default_namespace* -- sets the default XML namespace (for "xmlns").
698699 This option takes precedence over a default
699- namespace registered using
700+ namespace in nsmap or registered using
700701 register_namespace().
701702
702703 *method* -- either "xml" (default), "html, "text", or "c14n"
@@ -707,6 +708,14 @@ def write(self, file_or_filename,
707708 tag, otherwise they are emitted as a pair
708709 of start/end tags
709710
711+ *nsmap* -- a mapping of namespace prefixes to namespace uris. These
712+ prefixes take precedence over namespaces registered using
713+ register_namespace(). If provided, the default_namespace
714+ argument takes precedence over a default prefix ("")
715+ supplied in nsmap. All supplied entries will be declared
716+ in the root element (unless the "" prefix has been
717+ replaced by the default_namespace argument).
718+
710719 """
711720 if not method :
712721 method = "xml"
@@ -727,7 +736,11 @@ def write(self, file_or_filename,
727736 if method == "text" :
728737 _serialize_text (write , self ._root )
729738 else :
730- qnames , nsmap = _namespaces (self ._root , default_namespace )
739+ qnames , nsmap = _namespaces (
740+ self ._root ,
741+ default_namespace ,
742+ nsmap ,
743+ )
731744 serialize = _serialize [method ]
732745 serialize (write , self ._root , qnames , nsmap ,
733746 short_empty_elements = short_empty_elements )
@@ -802,6 +815,25 @@ def _make_new_ns_prefix(nsmap_scope, global_prefixes):
802815 i += 1
803816
804817
818+ def _find_default_namespace_attr_prefix (
819+ default_namespace ,
820+ nsmap ,
821+ global_prefixes ,
822+ ):
823+ # Search the provided nsmap for any prefixes for this uri that aren't the
824+ # default namespace ""
825+ for prefix , uri in nsmap .items ():
826+ if uri == default_namespace and prefix != "" :
827+ return prefix
828+
829+ # _namespace_map is a 1:1 mapping of uri -> prefix
830+ prefix = _namespace_map .get (default_namespace )
831+ if prefix and prefix not in nsmap :
832+ return prefix
833+
834+ return _make_new_ns_prefix (nsmap , global_prefixes )
835+
836+
805837def _qnames_iter (elem ):
806838 """Iterate through all the qualified names in elem"""
807839 seen_el_qnames = set ()
@@ -839,17 +871,25 @@ def _qnames_iter(elem):
839871 yield text .text , False
840872
841873
842- def _namespaces (elem , default_namespace = None ):
874+ def _namespaces (elem , default_namespace = None , nsmap = None ):
843875 # identify namespaces used in this tree
844876
845877 qnames = {None : (None , None )}
846878
847- nsmap = {}
848- uri_to_prefix = {}
849- default_namespace_attr_prefix = None
850- if default_namespace :
879+ if nsmap is None :
880+ nsmap = {}
881+ else :
882+ nsmap = nsmap .copy ()
883+
884+ if default_namespace is not None :
851885 nsmap ["" ] = default_namespace
852- uri_to_prefix [default_namespace ] = ""
886+
887+ # Multiple prefixes may be present for a single uri. This will select the
888+ # last prefix found in nsmap for a given uri.
889+ uri_to_prefix = {uri : prefix for prefix , uri in nsmap .items ()}
890+ if "" in nsmap :
891+ # but we make sure the default prefix always take precedence
892+ uri_to_prefix [nsmap ["" ]] = ""
853893
854894 global_prefixes = set (_namespace_map .values ())
855895 has_unqual_el = False
@@ -871,10 +911,11 @@ def _namespaces(elem, default_namespace=None):
871911 if not is_el and not prefix and not default_namespace_attr_prefix :
872912 # Find the alternative prefix to use with non-element
873913 # names
874- default_namespace_attr_prefix = _namespace_map .get (uri_and_name [0 ])
875- if not default_namespace_attr_prefix :
876- # if default_namespace_attr_prefix == "" or None
877- default_namespace_attr_prefix = _make_new_ns_prefix (nsmap , global_prefixes )
914+ default_namespace_attr_prefix = _find_default_namespace_attr_prefix (
915+ uri_and_name [0 ],
916+ nsmap ,
917+ global_prefixes ,
918+ )
878919 nsmap [default_namespace_attr_prefix ] = uri_and_name [0 ]
879920 # Don't add this uri to prefix mapping as it might override
880921 # the uri -> "" default mapping. We'll fix this up at the
@@ -1129,7 +1170,7 @@ def _escape_attrib_html(text):
11291170
11301171def tostring (element , encoding = None , method = None , * ,
11311172 xml_declaration = None , default_namespace = None ,
1132- short_empty_elements = True ):
1173+ short_empty_elements = True , nsmap = None ):
11331174 """Generate string representation of XML element.
11341175
11351176 All subelements are included. If encoding is "unicode", a string
@@ -1138,7 +1179,8 @@ def tostring(element, encoding=None, method=None, *,
11381179 *element* is an Element instance, *encoding* is an optional output
11391180 encoding defaulting to US-ASCII, *method* is an optional output which can
11401181 be one of "xml" (default), "html", "text" or "c14n", *default_namespace*
1141- sets the default XML namespace (for "xmlns").
1182+ sets the default XML namespace (for "xmlns"). *nsmap* is a mapping of
1183+ prefixes to namespaces.
11421184
11431185 Returns an (optionally) encoded string containing the XML data.
11441186
@@ -1148,7 +1190,8 @@ def tostring(element, encoding=None, method=None, *,
11481190 xml_declaration = xml_declaration ,
11491191 default_namespace = default_namespace ,
11501192 method = method ,
1151- short_empty_elements = short_empty_elements )
1193+ short_empty_elements = short_empty_elements ,
1194+ nsmap = nsmap )
11521195 return stream .getvalue ()
11531196
11541197class _ListDataStream (io .BufferedIOBase ):
@@ -1170,14 +1213,15 @@ def tell(self):
11701213
11711214def tostringlist (element , encoding = None , method = None , * ,
11721215 xml_declaration = None , default_namespace = None ,
1173- short_empty_elements = True ):
1216+ short_empty_elements = True , nsmap = None ):
11741217 lst = []
11751218 stream = _ListDataStream (lst )
11761219 ElementTree (element ).write (stream , encoding ,
11771220 xml_declaration = xml_declaration ,
11781221 default_namespace = default_namespace ,
11791222 method = method ,
1180- short_empty_elements = short_empty_elements )
1223+ short_empty_elements = short_empty_elements ,
1224+ nsmap = nsmap )
11811225 return lst
11821226
11831227
0 commit comments