Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Xml / System / Xml / Core / XmlTextEncoder.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XmlTextWriter.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7
8 using System;
9 using System.IO;
10 using System.Text;
11 using System.Diagnostics;
12 using System.Globalization;
13
14 namespace System.Xml {
15
16     // XmlTextEncoder
17     //
18     // This class does special handling of text content for XML.  For example
19     // it will replace special characters with entities whenever necessary.
20     internal class XmlTextEncoder {
21 //
22 // Fields
23 //
24         // output text writer
25         TextWriter      textWriter;
26
27         // true when writing out the content of attribute value
28         bool            inAttribute;
29
30         // quote char of the attribute (when inAttribute) 
31         char            quoteChar;
32
33         // caching of attribute value
34         StringBuilder   attrValue;
35         bool            cacheAttrValue; 
36
37         // XmlCharType
38         XmlCharType     xmlCharType;
39
40 //
41 // Constructor
42 //
43         internal XmlTextEncoder( TextWriter textWriter ) {
44             this.textWriter = textWriter;
45             this.quoteChar = '"';
46             this.xmlCharType = XmlCharType.Instance;
47         }
48
49 //
50 // Internal methods and properties
51 //
52         internal char QuoteChar {
53             set { 
54                 this.quoteChar = value;
55             }
56         }
57
58         internal void StartAttribute( bool cacheAttrValue ) {
59             this.inAttribute = true;
60             this.cacheAttrValue = cacheAttrValue;
61             if ( cacheAttrValue ) { 
62                 if ( attrValue == null ) {
63                     attrValue = new StringBuilder();
64                 }
65                 else {
66                     attrValue.Length = 0;
67                 }
68             }
69         }
70
71         internal void EndAttribute() {
72             if ( cacheAttrValue ) {
73                 attrValue.Length = 0;
74             }
75             this.inAttribute = false;
76             this.cacheAttrValue = false;
77         }
78
79         internal string AttributeValue {
80             get {
81                 if ( cacheAttrValue ) {
82                     return attrValue.ToString();
83                 }
84                 else {
85                     return String.Empty;
86                 }
87             }
88         }
89
90         internal void WriteSurrogateChar( char lowChar, char highChar ) {
91             if ( !XmlCharType.IsLowSurrogate(lowChar) ||
92                  !XmlCharType.IsHighSurrogate( highChar ) ) {
93                 throw XmlConvert.CreateInvalidSurrogatePairException( lowChar, highChar );
94             }
95
96             textWriter.Write( highChar );
97             textWriter.Write( lowChar );
98         }
99
100 #if FEATURE_NETCORE
101         [System.Security.SecurityCritical]
102 #endif
103         internal void Write( char[] array, int offset, int count ) {
104             if ( null == array ) {
105                 throw new ArgumentNullException("array");
106             }
107
108             if ( 0 > offset ) {
109                 throw new ArgumentOutOfRangeException("offset");
110             }
111
112             if ( 0 > count ) {
113                 throw new ArgumentOutOfRangeException("count");
114             }
115
116             if ( count > array.Length - offset ) {
117                 throw new ArgumentOutOfRangeException("count");
118             }
119
120             if ( cacheAttrValue ) {
121                 attrValue.Append( array, offset, count );
122             }
123
124             int endPos = offset + count;
125             int i = offset;
126             char ch = (char)0;
127             for (;;) {
128                 int startPos = i;
129                 unsafe {
130                     while ( i < endPos && ( xmlCharType.charProperties[ch = array[i]] & XmlCharType.fAttrValue ) != 0 ) { // ( xmlCharType.IsAttributeValueChar( ( ch = array[i] ) ) ) ) {
131                         i++;
132                     }
133                 }
134
135                 if ( startPos < i ) {
136                     textWriter.Write( array, startPos, i - startPos );
137                 }
138                 if ( i == endPos ) {
139                     break;
140                 }
141
142                 switch ( ch ) {
143                     case (char)0x9:
144                         textWriter.Write( ch );
145                         break;
146                     case (char)0xA:
147                     case (char)0xD:
148                         if ( inAttribute ) {
149                             WriteCharEntityImpl( ch );
150                         }
151                         else {
152                             textWriter.Write( ch );
153                         }
154                         break;
155
156                     case '<':
157                         WriteEntityRefImpl( "lt" );
158                         break;
159                     case '>':
160                         WriteEntityRefImpl( "gt" );
161                         break;
162                     case '&':
163                         WriteEntityRefImpl( "amp" );
164                         break;
165                     case '\'':
166                         if ( inAttribute && quoteChar == ch ) {
167                             WriteEntityRefImpl( "apos" );
168                         }
169                         else {
170                             textWriter.Write( '\'' );
171                         }
172                         break;
173                     case '"':
174                         if ( inAttribute && quoteChar == ch ) {
175                             WriteEntityRefImpl( "quot" );
176                         }
177                         else {
178                             textWriter.Write( '"' );
179                         }
180                         break;
181                     default:
182                         if ( XmlCharType.IsHighSurrogate( ch ) ) {
183                             if ( i + 1 < endPos ) {
184                                 WriteSurrogateChar( array[++i], ch );
185                             }
186                             else {
187                                 throw new ArgumentException( Res.GetString( Res.Xml_SurrogatePairSplit ) );
188                             }
189                         }
190                         else if ( XmlCharType.IsLowSurrogate( ch ) ) {
191                             throw XmlConvert.CreateInvalidHighSurrogateCharException( ch );
192                         }
193                         else {
194                             Debug.Assert( ( ch < 0x20 && !xmlCharType.IsWhiteSpace( ch ) ) || ( ch > 0xFFFD ) );
195                             WriteCharEntityImpl( ch );
196                         }
197                         break;
198                 }
199                 i++;
200             }
201         }
202
203         internal void WriteSurrogateCharEntity( char lowChar, char highChar ) {
204             if ( !XmlCharType.IsLowSurrogate( lowChar ) ||
205                  !XmlCharType.IsHighSurrogate( highChar ) ) {
206                 throw XmlConvert.CreateInvalidSurrogatePairException( lowChar, highChar );
207
208             }
209             int surrogateChar = XmlCharType.CombineSurrogateChar( lowChar, highChar );
210
211             if ( cacheAttrValue ) {
212                 attrValue.Append( highChar );
213                 attrValue.Append( lowChar );
214             }
215
216             textWriter.Write( "&#x" );
217             textWriter.Write( surrogateChar.ToString( "X", NumberFormatInfo.InvariantInfo ) );
218             textWriter.Write( ';' );
219         }
220
221 #if FEATURE_NETCORE
222         [System.Security.SecurityCritical]
223 #endif
224         internal void Write( string text ) {
225             if ( text == null ) {
226                 return;
227             }
228
229             if ( cacheAttrValue ) {
230                 attrValue.Append( text );
231             }
232
233             // scan through the string to see if there are any characters to be escaped
234             int len = text.Length;
235             int i = 0;
236             int startPos = 0;
237             char ch = (char)0;
238             for (;;) {
239                 unsafe {
240                     while ( i < len && ( xmlCharType.charProperties[ch = text[i]] & XmlCharType.fAttrValue ) != 0 ) { // ( xmlCharType.IsAttributeValueChar( ( ch = text[i] ) ) ) ) {
241                         i++;
242                     }
243                 }
244                 if ( i == len ) {
245                     // reached the end of the string -> write it whole out
246                     textWriter.Write( text );
247                     return;
248                 }
249                 if ( inAttribute ) {
250                     if ( ch == 0x9 ) {
251                         i++;
252                         continue;
253                     }
254                 }
255                 else {
256                     if ( ch == 0x9 || ch == 0xA || ch == 0xD || ch == '"' || ch == '\'' ) {
257                         i++;
258                         continue;
259                     }
260                 }
261                 // some character that needs to be escaped is found:
262                 break;
263             }
264
265             char[] helperBuffer = new char[256];
266             for (;;) {
267                 if ( startPos < i ) {
268                     WriteStringFragment( text, startPos, i - startPos, helperBuffer );
269                 }
270                 if ( i == len ) {
271                     break;
272                 }
273
274                 switch ( ch ) {
275                     case (char)0x9:
276                         textWriter.Write( ch );
277                         break;
278                     case (char)0xA:
279                     case (char)0xD:
280                         if ( inAttribute ) {
281                             WriteCharEntityImpl( ch );
282                         }
283                         else {
284                             textWriter.Write( ch );
285                         }
286                         break;
287                     case '<':
288                         WriteEntityRefImpl( "lt" );
289                         break;
290                     case '>':
291                         WriteEntityRefImpl( "gt" );
292                         break;
293                     case '&':
294                         WriteEntityRefImpl( "amp" );
295                         break;
296                     case '\'':
297                         if ( inAttribute && quoteChar == ch ) {
298                             WriteEntityRefImpl( "apos" );
299                         }
300                         else {
301                             textWriter.Write( '\'' );
302                         }
303                         break;
304                     case '"':
305                         if ( inAttribute && quoteChar == ch ) {
306                             WriteEntityRefImpl( "quot" );
307                         }
308                         else {
309                             textWriter.Write( '"' );
310                         }
311                         break;
312                     default:
313                         if ( XmlCharType.IsHighSurrogate( ch ) ) {
314                             if ( i + 1 < len ) {
315                                 WriteSurrogateChar( text[++i], ch );
316                             }
317                             else {
318                                 throw XmlConvert.CreateInvalidSurrogatePairException( text[i], ch );
319                             }
320                         }
321                         else if ( XmlCharType.IsLowSurrogate( ch ) ) {
322                             throw XmlConvert.CreateInvalidHighSurrogateCharException( ch );
323                         }
324                         else {
325                             Debug.Assert( ( ch < 0x20 && !xmlCharType.IsWhiteSpace( ch ) ) || ( ch > 0xFFFD ) );
326                             WriteCharEntityImpl( ch );
327                         }
328                         break;
329                 }
330                 i++;
331                 startPos = i;
332                 unsafe {
333                     while ( i < len && ( xmlCharType.charProperties[ch = text[i]] & XmlCharType.fAttrValue ) != 0 ) { // ( xmlCharType.IsAttributeValueChar( ( text[i] ) ) ) ) {
334                         i++;
335                     }
336                 }
337             }
338         }
339
340 #if FEATURE_NETCORE
341         [System.Security.SecurityCritical]
342 #endif
343         internal void WriteRawWithSurrogateChecking( string text ) {
344             if ( text == null ) {
345                 return;
346             }
347             if ( cacheAttrValue ) {
348                 attrValue.Append( text );
349             }
350
351             int len = text.Length;
352             int i = 0;
353             char ch = (char)0;
354
355             for (;;) {
356                 unsafe {
357                     while ( i < len && 
358                         ( ( xmlCharType.charProperties[ch = text[i]] & XmlCharType.fCharData ) != 0 // ( xmlCharType.IsCharData( ( ch = text[i] ) ) 
359                         || ch < 0x20 ) ) {
360                         i++;
361                     }
362                 }
363                 if ( i == len ) {
364                     break;
365                 }
366                 if ( XmlCharType.IsHighSurrogate( ch ) ) {
367                     if ( i + 1 < len ) {
368                         char lowChar = text[i+1];
369                         if ( XmlCharType.IsLowSurrogate( lowChar ) ) {
370                             i += 2;
371                             continue;
372                         }
373                         else {
374                             throw XmlConvert.CreateInvalidSurrogatePairException( lowChar, ch );
375                         }
376                     }
377                     throw new ArgumentException( Res.GetString( Res.Xml_InvalidSurrogateMissingLowChar ) );
378                 }
379                 else if ( XmlCharType.IsLowSurrogate( ch ) ) {
380                     throw XmlConvert.CreateInvalidHighSurrogateCharException( ch );
381                 }
382                 else {
383                     i++;
384                 }
385             }
386
387             textWriter.Write( text );
388             return;
389         }
390
391         internal void WriteRaw( string value ) {
392             if ( cacheAttrValue ) {
393                 attrValue.Append( value );
394             }
395             textWriter.Write( value );
396         }
397
398         internal void WriteRaw( char[] array, int offset, int count ) {
399             if ( null == array ) {
400                 throw new ArgumentNullException("array");
401             }
402
403             if ( 0 > count ) {
404                 throw new ArgumentOutOfRangeException("count");
405             }
406
407             if ( 0 > offset ) {
408                 throw new ArgumentOutOfRangeException("offset");
409             }
410
411             if ( count > array.Length - offset ) {
412                 throw new ArgumentOutOfRangeException("count");
413             }
414
415             if ( cacheAttrValue ) {
416                 attrValue.Append( array, offset, count );
417             }
418             textWriter.Write( array, offset, count );
419         }
420
421
422
423         internal void WriteCharEntity( char ch ) {
424             if ( XmlCharType.IsSurrogate(ch) ) {
425                 throw new ArgumentException( Res.GetString( Res.Xml_InvalidSurrogateMissingLowChar ) );
426             }
427
428             string strVal = ((int)ch).ToString( "X", NumberFormatInfo.InvariantInfo );
429             if ( cacheAttrValue ) { 
430                 attrValue.Append( "&#x" );
431                 attrValue.Append( strVal );
432                 attrValue.Append( ';' );
433             }
434             WriteCharEntityImpl( strVal );
435         }
436
437         internal void WriteEntityRef( string name ) {
438             if ( cacheAttrValue ) {
439                 attrValue.Append( '&' );
440                 attrValue.Append( name );
441                 attrValue.Append( ';' );
442             }
443             WriteEntityRefImpl( name );
444         }
445
446         internal void Flush() {
447             // 
448         }
449
450 //
451 // Private implementation methods
452 //
453         // This is a helper method to woraround the fact that TextWriter does not have a Write method 
454         // for fragment of a string such as Write( string, offset, count). 
455         // The string fragment will be written out by copying into a small helper buffer and then 
456         // calling textWriter to write out the buffer.
457         private void WriteStringFragment( string str, int offset, int count, char[] helperBuffer ) {
458             int bufferSize = helperBuffer.Length;
459             while ( count > 0 ) {
460                 int copyCount = count;
461                 if ( copyCount > bufferSize ) {
462                     copyCount = bufferSize;
463                 }
464
465                 str.CopyTo( offset, helperBuffer, 0, copyCount );
466                 textWriter.Write( helperBuffer, 0, copyCount );
467                 offset += copyCount;
468                 count -= copyCount;
469             }
470         }
471
472         private void WriteCharEntityImpl( char ch ) {
473             WriteCharEntityImpl( ((int)ch).ToString( "X", NumberFormatInfo.InvariantInfo ) );
474         }
475
476         private void WriteCharEntityImpl( string strVal ) {
477             textWriter.Write( "&#x" );
478             textWriter.Write( strVal );
479             textWriter.Write( ';' );
480         }
481
482         private void WriteEntityRefImpl( string name ) {
483             textWriter.Write( '&' );
484             textWriter.Write( name );
485             textWriter.Write( ';' );
486         }
487     }
488 }