@@ -743,9 +743,10 @@ def write(self, file_or_filename,
743743 if method == "text" :
744744 _serialize_text (write , self ._root )
745745 else :
746- qnames , namespaces = _namespaces (self ._root , default_namespace )
746+ qnames , attr_qnames , namespaces = _namespaces (
747+ self ._root , default_namespace )
747748 serialize = _serialize [method ]
748- serialize (write , self ._root , qnames , namespaces ,
749+ serialize (write , self ._root , qnames , attr_qnames , namespaces ,
749750 short_empty_elements = short_empty_elements )
750751
751752# --------------------------------------------------------------------
@@ -806,28 +807,57 @@ def _namespaces(elem, default_namespace=None):
806807
807808 # maps qnames to *encoded* prefix:local names
808809 qnames = {None : None }
810+ # The default namespace declaration does not apply to attribute names,
811+ # so they are encoded separately: an unqualified name is left as is,
812+ # and a qualified name always gets a prefix.
813+ attr_qnames = {None : None } if default_namespace else qnames
809814
810- # maps uri:s to prefixes
815+ # maps prefixes to uri:s
811816 namespaces = {}
817+ # maps uri:s to prefixes, "" is the prefix of the default namespace
818+ prefixes = {}
819+ # maps uri:s to prefixes usable in attribute names
820+ attr_prefixes = {} if default_namespace else prefixes
812821 if default_namespace :
813- namespaces [default_namespace ] = ""
814-
815- def add_qname (qname ):
822+ namespaces ["" ] = default_namespace
823+ prefixes [default_namespace ] = ""
824+
825+ def get_prefix (uri , isattr ):
826+ # find or create the prefix for the namespace uri
827+ if isattr :
828+ prefix = attr_prefixes .get (uri )
829+ if prefix is None :
830+ # the default namespace is of no use for an attribute name
831+ prefix = prefixes .get (uri ) or None
832+ else :
833+ prefix = prefixes .get (uri )
834+ if prefix is not None :
835+ return prefix
836+ prefix = _namespace_map .get (uri )
837+ if not prefix :
838+ prefix = "ns%d" % len (namespaces )
839+ if prefix != "xml" :
840+ namespaces [prefix ] = uri
841+ if isattr :
842+ attr_prefixes [uri ] = prefix
843+ prefixes .setdefault (uri , prefix )
844+ return prefix
845+
846+ def add_qname (qname , isattr = False ):
816847 # calculate serialized qname representation
817848 try :
818849 if qname [:1 ] == "{" :
819850 uri , tag = qname [1 :].rsplit ("}" , 1 )
820- prefix = namespaces .get (uri )
821- if prefix is None :
822- prefix = _namespace_map .get (uri )
823- if prefix is None :
824- prefix = "ns%d" % len (namespaces )
825- if prefix != "xml" :
826- namespaces [uri ] = prefix
851+ prefix = get_prefix (uri , isattr )
827852 if prefix :
828- qnames [qname ] = "%s:%s" % (prefix , tag )
853+ if isattr :
854+ attr_qnames [qname ] = "%s:%s" % (prefix , tag )
855+ else :
856+ qnames [qname ] = "%s:%s" % (prefix , tag )
829857 else :
830858 qnames [qname ] = tag # default element
859+ elif isattr :
860+ attr_qnames [qname ] = qname
831861 else :
832862 if default_namespace :
833863 # FIXME: can this be handled in XML 1.0?
@@ -853,16 +883,16 @@ def add_qname(qname):
853883 for key , value in elem .items ():
854884 if isinstance (key , QName ):
855885 key = key .text
856- if key not in qnames :
857- add_qname (key )
886+ if key not in attr_qnames :
887+ add_qname (key , isattr = True )
858888 if isinstance (value , QName ) and value .text not in qnames :
859889 add_qname (value .text )
860890 text = elem .text
861891 if isinstance (text , QName ) and text .text not in qnames :
862892 add_qname (text .text )
863- return qnames , namespaces
893+ return qnames , attr_qnames , namespaces
864894
865- def _serialize_xml (write , elem , qnames , namespaces ,
895+ def _serialize_xml (write , elem , qnames , attr_qnames , namespaces ,
866896 short_empty_elements , ** kwargs ):
867897 tag = elem .tag
868898 text = elem .text
@@ -876,15 +906,14 @@ def _serialize_xml(write, elem, qnames, namespaces,
876906 if text :
877907 write (_escape_cdata (text ))
878908 for e in elem :
879- _serialize_xml (write , e , qnames , None ,
909+ _serialize_xml (write , e , qnames , attr_qnames , None ,
880910 short_empty_elements = short_empty_elements )
881911 else :
882912 write ("<" + tag )
883913 items = list (elem .items ())
884914 if items or namespaces :
885915 if namespaces :
886- for v , k in sorted (namespaces .items (),
887- key = lambda x : x [1 ]): # sort on prefix
916+ for k , v in sorted (namespaces .items ()): # sort on prefix
888917 if k :
889918 k = ":" + k
890919 write (" xmlns%s=\" %s\" " % (
@@ -898,13 +927,13 @@ def _serialize_xml(write, elem, qnames, namespaces,
898927 v = qnames [v .text ]
899928 else :
900929 v = _escape_attrib (v )
901- write (" %s=\" %s\" " % (qnames [k ], v ))
930+ write (" %s=\" %s\" " % (attr_qnames [k ], v ))
902931 if text or len (elem ) or not short_empty_elements :
903932 write (">" )
904933 if text :
905934 write (_escape_cdata (text ))
906935 for e in elem :
907- _serialize_xml (write , e , qnames , None ,
936+ _serialize_xml (write , e , qnames , attr_qnames , None ,
908937 short_empty_elements = short_empty_elements )
909938 write ("</" + tag + ">" )
910939 else :
@@ -919,7 +948,7 @@ def _serialize_xml(write, elem, qnames, namespaces,
919948 "img" , "input" , "isindex" , "link" , "meta" , "param" , "source" ,
920949 "track" , "wbr" , "plaintext" }
921950
922- def _serialize_html (write , elem , qnames , namespaces , ** kwargs ):
951+ def _serialize_html (write , elem , qnames , attr_qnames , namespaces , ** kwargs ):
923952 tag = elem .tag
924953 text = elem .text
925954 if tag is Comment :
@@ -932,14 +961,13 @@ def _serialize_html(write, elem, qnames, namespaces, **kwargs):
932961 if text :
933962 write (_escape_cdata (text ))
934963 for e in elem :
935- _serialize_html (write , e , qnames , None )
964+ _serialize_html (write , e , qnames , attr_qnames , None )
936965 else :
937966 write ("<" + tag )
938967 items = list (elem .items ())
939968 if items or namespaces :
940969 if namespaces :
941- for v , k in sorted (namespaces .items (),
942- key = lambda x : x [1 ]): # sort on prefix
970+ for k , v in sorted (namespaces .items ()): # sort on prefix
943971 if k :
944972 k = ":" + k
945973 write (" xmlns%s=\" %s\" " % (
@@ -949,7 +977,7 @@ def _serialize_html(write, elem, qnames, namespaces, **kwargs):
949977 for k , v in items :
950978 if isinstance (k , QName ):
951979 k = k .text
952- k = qnames [k ]
980+ k = attr_qnames [k ]
953981 if v is None :
954982 write (" %s" % k ) # empty attr
955983 else :
@@ -966,7 +994,7 @@ def _serialize_html(write, elem, qnames, namespaces, **kwargs):
966994 else :
967995 write (_escape_cdata (text ))
968996 for e in elem :
969- _serialize_html (write , e , qnames , None )
997+ _serialize_html (write , e , qnames , attr_qnames , None )
970998 if ltag not in HTML_EMPTY :
971999 write ("</" + tag + ">" )
9721000 if elem .tail :
0 commit comments