@@ -724,9 +724,9 @@ def write(self, file_or_filename,
724724 if method == "text" :
725725 _serialize_text (write , self ._root )
726726 else :
727- qnames , namespaces = _namespaces (self ._root , default_namespace )
727+ qnames , nsmap = _namespaces (self ._root , default_namespace )
728728 serialize = _serialize [method ]
729- serialize (write , self ._root , qnames , namespaces ,
729+ serialize (write , self ._root , qnames , nsmap ,
730730 short_empty_elements = short_empty_elements )
731731
732732 def write_c14n (self , file ):
@@ -787,84 +787,113 @@ def _get_writer(file_or_filename, encoding):
787787 yield file .write , encoding
788788
789789
790- def _make_new_ns_prefix (nsmap_scope , seen_prefixes ):
790+ def _make_new_ns_prefix (nsmap_scope , global_prefixes ):
791791 i = len (nsmap_scope )
792792 while True :
793793 prefix = f"ns{ i } "
794- if prefix not in seen_prefixes :
794+ if (
795+ prefix not in nsmap_scope
796+ and prefix not in global_prefixes
797+ ):
795798 return prefix
796799 i += 1
797800
798801
799802def _qnames_iter (elem ):
800803 """Iterate through all the qualified names in elem"""
801- seen_qnames = set ()
804+ seen_el_qnames = set ()
805+ seen_other_qnames = set ()
802806 for elem in elem .iter ():
803807 tag = elem .tag
804808 if isinstance (tag , str ):
805- if tag not in seen_qnames :
806- seen_qnames .add (tag )
807- yield tag
809+ if tag not in seen_el_qnames :
810+ seen_el_qnames .add (tag )
811+ yield tag , True
808812 elif isinstance (tag , QName ):
809813 tag = tag .text
810- if tag not in seen_qnames :
811- seen_qnames .add (tag )
812- yield tag
814+ if tag not in seen_el_qnames :
815+ seen_el_qnames .add (tag )
816+ yield tag , True
813817 elif tag is not None and tag is not Comment and tag is not PI :
814818 _raise_serialization_error (tag )
815819
816820 for key , value in elem .items ():
817821 if isinstance (key , QName ):
818822 key = key .text
819- if key not in seen_qnames :
820- seen_qnames .add (key )
821- yield key
823+ if key not in seen_other_qnames :
824+ seen_other_qnames .add (key )
825+ yield key , False
822826
823827 if isinstance (value , QName ):
824- if value .text not in seen_qnames :
825- seen_qnames .add (value .text )
826- yield value .text
828+ if value .text not in seen_other_qnames :
829+ seen_other_qnames .add (value .text )
830+ yield value .text , False
827831
828832 text = elem .text
829833 if isinstance (text , QName ):
830- if text .text not in seen_qnames :
831- seen_qnames .add (text .text )
832- yield text .text
834+ if text .text not in seen_other_qnames :
835+ seen_other_qnames .add (text .text )
836+ yield text .text , False
833837
834838
835839def _namespaces (elem , default_namespace = None ):
836840 # identify namespaces used in this tree
837841
838- # maps qnames to *encoded* prefix:local names
839- qnames = {None : None }
842+ qnames = {None : (None , None )}
840843
841- # maps uri:s to prefixes
842- namespaces = {}
844+ nsmap = {}
845+ uri_to_prefix = {}
846+ default_namespace_attr_prefix = None
843847 if default_namespace :
844- namespaces [default_namespace ] = ""
848+ nsmap ["" ] = default_namespace
849+ uri_to_prefix [default_namespace ] = ""
845850
846- seen_prefixes = set (_namespace_map .values ())
851+ global_prefixes = set (_namespace_map .values ())
847852 has_unqual_el = False
848- for qname in _qnames_iter (elem ):
853+ default_namespace_attr_prefix = None
854+ for qname , is_el in _qnames_iter (elem ):
849855 try :
850856 if qname [:1 ] == "{" :
851857 uri_and_name = qname [1 :].rsplit ("}" , 1 )
852858
853- prefix = namespaces .get (uri_and_name [0 ])
859+ prefix = uri_to_prefix .get (uri_and_name [0 ])
854860 if prefix is None :
855861 prefix = _namespace_map .get (uri_and_name [0 ])
856862 if prefix is None :
857- prefix = _make_new_ns_prefix (namespaces , seen_prefixes )
858- seen_prefixes .add (prefix )
859- namespaces [uri_and_name [0 ]] = prefix
863+ prefix = _make_new_ns_prefix (nsmap , global_prefixes )
864+ if prefix or is_el :
865+ nsmap [prefix ] = uri_and_name [0 ]
866+ uri_to_prefix [uri_and_name [0 ]] = prefix
867+
868+ if not is_el and not prefix and not default_namespace_attr_prefix :
869+ # Find the alternative prefix to use with non-element
870+ # names
871+ default_namespace_attr_prefix = _namespace_map .get (uri_and_name [0 ])
872+ if not default_namespace_attr_prefix :
873+ # if default_namespace_attr_prefix == "" or None
874+ default_namespace_attr_prefix = _make_new_ns_prefix (nsmap , global_prefixes )
875+ nsmap [default_namespace_attr_prefix ] = uri_and_name [0 ]
876+ # Don't add this uri to prefix mapping as it might override
877+ # the uri -> "" default mapping. We'll fix this up at the
878+ # end of the fn.
879+ # uri_to_prefix[uri_and_name[0]] = default_namespace_attr_prefix
860880
861881 if prefix :
862- qnames [qname ] = f"{ prefix } :{ uri_and_name [1 ]} "
882+ name = f"{ prefix } :{ uri_and_name [1 ]} "
883+ qnames [qname ] = (name , name )
863884 else :
864- qnames [qname ] = uri_and_name [1 ] # default element
885+ # default_namespace_attr_prefix may not be defined yet but
886+ # this bad entry will be replaced if an attribute that
887+ # requires default_namespace_attr_prefix is found later.
888+ qnames [qname ] = (
889+ uri_and_name [1 ], # default element
890+ f"{ default_namespace_attr_prefix } :{ uri_and_name [1 ]} " , # non-element qname
891+ )
865892 else :
866- qnames [qname ] = qname
867- has_unqual_el = True
893+ # Unqualified qname
894+ qnames [qname ] = (qname , qname )
895+ if is_el :
896+ has_unqual_el = True
868897 except TypeError :
869898 _raise_serialization_error (qname )
870899
@@ -874,14 +903,13 @@ def _namespaces(elem, default_namespace=None):
874903 "cannot use non-qualified names with default_namespace option"
875904 )
876905
877- # This namespace doesn't need to be declared but may be used to prefix
878- # names so let's remove it if it has been used
879- if "http://www.w3.org/XML/1998/namespace" in namespaces :
880- del namespaces ["http://www.w3.org/XML/1998/namespace" ]
881- return qnames , namespaces
882-
906+ # The xml prefix doesn't need to be declared but may have been used to
907+ # prefix names. Let's remove it if it has been used
908+ if "xml" in nsmap :
909+ del nsmap ["xml" ]
910+ return qnames , nsmap
883911
884- def _serialize_xml (write , elem , qnames , namespaces ,
912+ def _serialize_xml (write , elem , qnames , nsmap ,
885913 short_empty_elements , ** kwargs ):
886914 tag = elem .tag
887915 text = elem .text
@@ -890,7 +918,7 @@ def _serialize_xml(write, elem, qnames, namespaces,
890918 elif tag is ProcessingInstruction :
891919 write ("<?%s?>" % text )
892920 else :
893- tag = qnames [tag ]
921+ tag = qnames [tag ][ 0 ]
894922 if tag is None :
895923 if text :
896924 write (_escape_cdata (text ))
@@ -900,10 +928,9 @@ def _serialize_xml(write, elem, qnames, namespaces,
900928 else :
901929 write ("<" + tag )
902930 items = list (elem .items ())
903- if items or namespaces :
904- if namespaces :
905- for v , k in sorted (namespaces .items (),
906- key = lambda x : x [1 ]): # sort on prefix
931+ if items or nsmap :
932+ if nsmap :
933+ for k , v in sorted (nsmap .items ()):
907934 if k :
908935 k = ":" + k
909936 write (" xmlns%s=\" %s\" " % (
@@ -914,10 +941,10 @@ def _serialize_xml(write, elem, qnames, namespaces,
914941 if isinstance (k , QName ):
915942 k = k .text
916943 if isinstance (v , QName ):
917- v = qnames [v .text ]
944+ v = qnames [v .text ][ 1 ]
918945 else :
919946 v = _escape_attrib (v )
920- write (" %s=\" %s\" " % (qnames [k ], v ))
947+ write (" %s=\" %s\" " % (qnames [k ][ 1 ] , v ))
921948 if text or len (elem ) or not short_empty_elements :
922949 write (">" )
923950 if text :
@@ -935,15 +962,15 @@ def _serialize_xml(write, elem, qnames, namespaces,
935962 "img" , "input" , "isindex" , "link" , "meta" , "param" , "source" ,
936963 "track" , "wbr" }
937964
938- def _serialize_html (write , elem , qnames , namespaces , ** kwargs ):
965+ def _serialize_html (write , elem , qnames , nsmap , ** kwargs ):
939966 tag = elem .tag
940967 text = elem .text
941968 if tag is Comment :
942969 write ("<!--%s-->" % _escape_cdata (text ))
943970 elif tag is ProcessingInstruction :
944971 write ("<?%s?>" % _escape_cdata (text ))
945972 else :
946- tag = qnames [tag ]
973+ tag = qnames [tag ][ 0 ]
947974 if tag is None :
948975 if text :
949976 write (_escape_cdata (text ))
@@ -952,10 +979,9 @@ def _serialize_html(write, elem, qnames, namespaces, **kwargs):
952979 else :
953980 write ("<" + tag )
954981 items = list (elem .items ())
955- if items or namespaces :
956- if namespaces :
957- for v , k in sorted (namespaces .items (),
958- key = lambda x : x [1 ]): # sort on prefix
982+ if items or nsmap :
983+ if nsmap :
984+ for k , v in sorted (nsmap .items ()):
959985 if k :
960986 k = ":" + k
961987 write (" xmlns%s=\" %s\" " % (
@@ -966,11 +992,11 @@ def _serialize_html(write, elem, qnames, namespaces, **kwargs):
966992 if isinstance (k , QName ):
967993 k = k .text
968994 if isinstance (v , QName ):
969- v = qnames [v .text ]
995+ v = qnames [v .text ][ 1 ]
970996 else :
971997 v = _escape_attrib_html (v )
972998 # FIXME: handle boolean attributes
973- write (" %s=\" %s\" " % (qnames [k ], v ))
999+ write (" %s=\" %s\" " % (qnames [k ][ 1 ] , v ))
9741000 write (">" )
9751001 ltag = tag .lower ()
9761002 if text :
0 commit comments