@@ -130,6 +130,12 @@ where
130130}
131131
132132/// Escapes XSD simple type value
133+ ///
134+ /// XML EOL normalization (Section 2.11) converts \r to \n before parsing, and
135+ /// attribute-value normalization (Section 3.3.3) converts \r, \n, \t to space.
136+ /// We escape these as character references (e.g. ) which bypass normalization,
137+ /// preserving the original characters on round-trip as seen in other implementations
138+ /// such as libxml2.
133139fn escape_list < W > ( mut writer : W , value : & str , target : QuoteTarget , level : QuoteLevel ) -> fmt:: Result
134140where
135141 W : Write ,
@@ -146,31 +152,49 @@ where
146152 }
147153 Ok ( ( ) )
148154 }
149- ( _, Full ) => escape_into ( writer, value, |ch| match ch {
155+ ( Text , Full ) => escape_into ( writer, value, |ch| match ch {
156+ // XML EOL normalization (Section 2.11) would convert \r to \n
157+ b'\r' => true ,
150158 // Required characters to escape
151159 b'&' | b'<' | b'>' | b'\'' | b'\"' => true ,
152160 _ => false ,
153161 } ) ,
154- //----------------------------------------------------------------------
155162 ( Text , Partial ) => escape_into ( writer, value, |ch| match ch {
163+ // XML EOL normalization (Section 2.11) would convert \r to \n
164+ b'\r' => true ,
156165 // Required characters to escape
157166 b'&' | b'<' | b'>' => true ,
158167 _ => false ,
159168 } ) ,
160169 ( Text , Minimal ) => escape_into ( writer, value, |ch| match ch {
170+ // XML EOL normalization (Section 2.11) would convert \r to \n
171+ b'\r' => true ,
161172 // Required characters to escape
162173 b'&' | b'<' => true ,
163174 _ => false ,
164175 } ) ,
165176 //----------------------------------------------------------------------
177+ ( _, Full ) => escape_into ( writer, value, |ch| match ch {
178+ // XML EOL normalization (Section 2.11) and attribute-value normalization
179+ // (Section 3.3.3) would convert these to \n and space respectively
180+ b'\r' | b'\n' | b'\t' => true ,
181+ // Required characters to escape
182+ b'&' | b'<' | b'>' | b'\'' | b'\"' => true ,
183+ _ => false ,
184+ } ) ,
185+ //----------------------------------------------------------------------
166186 ( DoubleQAttr , Partial ) => escape_into ( writer, value, |ch| match ch {
187+ // XML EOL / attribute-value normalization (Section 2.11, Section 3.3.3)
188+ b'\r' | b'\n' | b'\t' => true ,
167189 // Required characters to escape
168190 b'&' | b'<' | b'>' => true ,
169191 // Double quoted attribute should escape quote
170192 b'"' => true ,
171193 _ => false ,
172194 } ) ,
173195 ( DoubleQAttr , Minimal ) => escape_into ( writer, value, |ch| match ch {
196+ // XML EOL / attribute-value normalization (Section 2.11, Section 3.3.3)
197+ b'\r' | b'\n' | b'\t' => true ,
174198 // Required characters to escape
175199 b'&' | b'<' => true ,
176200 // Double quoted attribute should escape quote
@@ -179,13 +203,17 @@ where
179203 } ) ,
180204 //----------------------------------------------------------------------
181205 ( SingleQAttr , Partial ) => escape_into ( writer, value, |ch| match ch {
206+ // XML EOL / attribute-value normalization (Section 2.11, Section 3.3.3)
207+ b'\r' | b'\n' | b'\t' => true ,
182208 // Required characters to escape
183209 b'&' | b'<' | b'>' => true ,
184210 // Single quoted attribute should escape quote
185211 b'\'' => true ,
186212 _ => false ,
187213 } ) ,
188214 ( SingleQAttr , Minimal ) => escape_into ( writer, value, |ch| match ch {
215+ // XML EOL / attribute-value normalization (Section 2.11, Section 3.3.3)
216+ b'\r' | b'\n' | b'\t' => true ,
189217 // Required characters to escape
190218 b'&' | b'<' => true ,
191219 // Single quoted attribute should escape quote
@@ -893,7 +921,7 @@ mod tests {
893921 fn text ( ) {
894922 assert_eq ! (
895923 escape_list( "text<\" '&> \t \n \r text" , QuoteTarget :: Text , QuoteLevel :: Full ) ,
896- "text<"'&> \t \n \r text "
924+ "text<"'&> \t \n text "
897925 ) ;
898926 }
899927
@@ -905,7 +933,7 @@ mod tests {
905933 QuoteTarget :: DoubleQAttr ,
906934 QuoteLevel :: Full
907935 ) ,
908- "text<"'&> \t \n \r text "
936+ "text<"'&> 	 text "
909937 ) ;
910938 }
911939
@@ -917,7 +945,7 @@ mod tests {
917945 QuoteTarget :: SingleQAttr ,
918946 QuoteLevel :: Full
919947 ) ,
920- "text<"'&> \t \n \r text "
948+ "text<"'&> 	 text "
921949 ) ;
922950 }
923951 }
@@ -934,7 +962,7 @@ mod tests {
934962 QuoteTarget :: Text ,
935963 QuoteLevel :: Partial
936964 ) ,
937- "text<\" '&> \t \n \r text "
965+ "text<\" '&> \t \n text "
938966 ) ;
939967 }
940968
@@ -946,7 +974,7 @@ mod tests {
946974 QuoteTarget :: DoubleQAttr ,
947975 QuoteLevel :: Partial
948976 ) ,
949- "text<"'&> \t \n \r text "
977+ "text<"'&> 	 text "
950978 ) ;
951979 }
952980
@@ -958,7 +986,7 @@ mod tests {
958986 QuoteTarget :: SingleQAttr ,
959987 QuoteLevel :: Partial
960988 ) ,
961- "text<\" '&> \t \n \r text "
989+ "text<\" '&> 	 text "
962990 ) ;
963991 }
964992 }
@@ -975,7 +1003,7 @@ mod tests {
9751003 QuoteTarget :: Text ,
9761004 QuoteLevel :: Minimal
9771005 ) ,
978- "text<\" '&> \t \n \r text "
1006+ "text<\" '&> \t \n text "
9791007 ) ;
9801008 }
9811009
@@ -987,7 +1015,7 @@ mod tests {
9871015 QuoteTarget :: DoubleQAttr ,
9881016 QuoteLevel :: Minimal
9891017 ) ,
990- "text<"'&> \t \n \r text "
1018+ "text<"'&> 	 text "
9911019 ) ;
9921020 }
9931021
@@ -999,7 +1027,7 @@ mod tests {
9991027 QuoteTarget :: SingleQAttr ,
10001028 QuoteLevel :: Minimal
10011029 ) ,
1002- "text<\" '&> \t \n \r text "
1030+ "text<\" '&> 	 text "
10031031 ) ;
10041032 }
10051033 }
0 commit comments