879e99e0933f8d851b946a55c506f2f2f74dc790
[mono.git] / mcs / class / referencesource / System.Xml / System / Xml / Core / XmlCharCheckingWriter.cs
1
2
3 //------------------------------------------------------------------------------
4 // <copyright file="XmlCharCheckingWriter.cs" company="Microsoft">
5 //     Copyright (c) Microsoft Corporation.  All rights reserved.
6 // </copyright>
7 // <owner current="true" primary="true">Microsoft</owner>
8 //------------------------------------------------------------------------------
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 using System;
32 using System.IO;
33 using System.Text;
34 using System.Xml.Schema;
35 using System.Collections;
36 using System.Diagnostics;
37
38
39
40
41
42
43 namespace System.Xml {
44
45     //
46     // XmlCharCheckingWriter
47     //
48     internal partial class XmlCharCheckingWriter : XmlWrappingWriter {
49
50
51 //
52 // Fields
53 //
54         bool checkValues;
55         bool checkNames;
56         bool replaceNewLines;
57         string newLineChars;
58
59         XmlCharType xmlCharType;
60 // 
61 // Constructor
62 //
63         internal XmlCharCheckingWriter( XmlWriter baseWriter, bool checkValues, bool checkNames, bool replaceNewLines, string newLineChars ) 
64             : base( baseWriter ) {
65
66             Debug.Assert( checkValues || replaceNewLines );
67             this.checkValues = checkValues;
68             this.checkNames = checkNames;
69             this.replaceNewLines = replaceNewLines;
70             this.newLineChars = newLineChars;
71
72             if ( checkValues ) {
73                 xmlCharType = XmlCharType.Instance;
74             }
75         }
76         
77 //
78 // XmlWriter implementation
79 //
80
81         public override XmlWriterSettings Settings { 
82             get {
83                 XmlWriterSettings s = base.writer.Settings;
84                 s = ( s != null) ? (XmlWriterSettings)s.Clone() : new XmlWriterSettings();
85
86                 if ( checkValues ) {
87                     s.CheckCharacters = true;
88                 }
89                 if ( replaceNewLines ) {
90                     s.NewLineHandling = NewLineHandling.Replace;
91                     s.NewLineChars = newLineChars;
92                 }
93                 s.ReadOnly = true;
94                 return s;
95             }
96         }
97
98
99         public override void WriteDocType( string name, string pubid, string sysid, string subset ) { 
100             if ( checkNames ) {
101                 ValidateQName( name );
102             }
103             if ( checkValues ) {
104                 if ( pubid != null ) {
105                     int i;
106                     if ( ( i = xmlCharType.IsPublicId( pubid ) ) >= 0 ) {
107                         throw XmlConvert.CreateInvalidCharException( pubid, i );
108                     }
109                 }
110                 if ( sysid != null ) {
111                     CheckCharacters( sysid );
112                 }
113                 if ( subset != null ) {
114                     CheckCharacters( subset );
115                 }
116             }
117             if ( replaceNewLines ) {
118                 sysid = ReplaceNewLines( sysid );
119                 pubid = ReplaceNewLines( pubid );
120                 subset = ReplaceNewLines( subset );
121             }
122             writer.WriteDocType( name, pubid, sysid, subset );
123         }
124
125         public override void WriteStartElement( string prefix, string localName, string ns ) {
126             if ( checkNames ) {
127                 if ( localName == null || localName.Length == 0 ) {
128                     throw new ArgumentException( Res.GetString( Res.Xml_EmptyLocalName ) );
129                 }
130                 ValidateNCName( localName );
131
132                 if ( prefix != null && prefix.Length > 0 ) {
133                     ValidateNCName( prefix );
134                 }
135             }
136             writer.WriteStartElement( prefix, localName, ns );
137         }
138
139         public override void WriteStartAttribute( string prefix, string localName, string ns ) {
140             if ( checkNames ) {
141                 if ( localName == null || localName.Length == 0 ) {
142                     throw new ArgumentException( Res.GetString( Res.Xml_EmptyLocalName ) );
143                 }
144                 ValidateNCName( localName );
145
146                 if ( prefix != null && prefix.Length > 0 ) {
147                     ValidateNCName( prefix );
148                 }
149             }
150             writer.WriteStartAttribute( prefix, localName, ns );
151         }
152
153         public override void WriteCData( string text ) {
154             if ( text != null ) {
155                 if ( checkValues ) {
156                     CheckCharacters( text );
157                 }
158                 if ( replaceNewLines ) {
159                     text = ReplaceNewLines( text );
160                 }
161                 int i;
162                 while ( ( i = text.IndexOf( "]]>", StringComparison.Ordinal ) ) >= 0 ) {
163                     writer.WriteCData( text.Substring( 0, i + 2 ) );
164                     text = text.Substring( i + 2 );
165                 }
166             }
167             writer.WriteCData( text );
168         }
169
170         public override void WriteComment( string text ) {
171             if ( text != null ) {
172                 if ( checkValues ) {
173                     CheckCharacters( text );
174                     text = InterleaveInvalidChars( text, '-', '-' );
175                 }
176                 if ( replaceNewLines ) {
177                     text = ReplaceNewLines( text );
178                 }
179             }
180             writer.WriteComment( text );
181         }
182
183         public override void WriteProcessingInstruction( string name, string text ) {
184             if ( checkNames ) {
185                 ValidateNCName( name );
186             }
187             if ( text != null ) {
188                 if ( checkValues ) {
189                     CheckCharacters( text );
190                     text = InterleaveInvalidChars( text, '?', '>' );
191                 }
192                 if ( replaceNewLines ) {
193                     text = ReplaceNewLines( text );
194                 }
195             }
196             writer.WriteProcessingInstruction( name, text );
197         }
198
199         public override void WriteEntityRef( string name ) {
200             if ( checkNames ) {
201                 ValidateQName( name );
202             }
203             writer.WriteEntityRef( name );
204         }
205
206         public override void WriteWhitespace( string ws ) {
207             if ( ws == null ) {
208                 ws = string.Empty;
209             }
210             // "checkNames" is intentional here; if false, the whitespaces are checked in XmlWellformedWriter
211             if ( checkNames ) {
212                 int i;
213                 if ( ( i = xmlCharType.IsOnlyWhitespaceWithPos( ws ) ) != -1 ) {
214                     throw new ArgumentException( Res.GetString( Res.Xml_InvalidWhitespaceCharacter, XmlException.BuildCharExceptionArgs( ws, i ) ) );
215                 }
216             }
217             if ( replaceNewLines ) {
218                 ws = ReplaceNewLines( ws );
219             }
220             writer.WriteWhitespace( ws );
221         }
222
223         public override void WriteString( string text ) {
224             if ( text != null ) {
225                 if ( checkValues ) {
226                     CheckCharacters( text );
227                 }
228                 if ( replaceNewLines && WriteState != WriteState.Attribute ) {
229                     text = ReplaceNewLines( text );
230                 }
231             }
232             writer.WriteString( text );
233         }
234
235         public override void WriteSurrogateCharEntity( char lowChar, char highChar ) {
236             writer.WriteSurrogateCharEntity( lowChar, highChar );
237         }
238
239         public override void WriteChars( char[] buffer, int index, int count ) {
240             if (buffer == null) {
241                 throw new ArgumentNullException("buffer");
242             }
243             if (index < 0) {
244                 throw new ArgumentOutOfRangeException("index");
245             }
246             if (count < 0) {
247                 throw new ArgumentOutOfRangeException("count");
248             }
249             if (count > buffer.Length - index) {
250                 throw new ArgumentOutOfRangeException("count");
251             }
252
253             if (checkValues) {
254                 CheckCharacters( buffer, index, count );
255             }
256             if ( replaceNewLines && WriteState != WriteState.Attribute ) {
257                 string text = ReplaceNewLines( buffer, index, count );
258                 if ( text != null ) {
259                     WriteString( text );
260                     return;
261                 }
262             }
263             writer.WriteChars( buffer, index, count );
264         }
265
266         public override void WriteNmToken( string name ) { 
267             if ( checkNames ) {
268                 if ( name == null || name.Length == 0 ) {
269                     throw new ArgumentException( Res.GetString( Res.Xml_EmptyName ) );
270                 }
271                 XmlConvert.VerifyNMTOKEN( name );
272             }
273             writer.WriteNmToken( name );
274         }
275
276         public override void WriteName( string name ) {
277             if ( checkNames ) {
278                 XmlConvert.VerifyQName( name, ExceptionType.XmlException );
279             }
280             writer.WriteName( name );
281         }
282
283         public override void WriteQualifiedName( string localName, string ns ) {
284             if ( checkNames ) {
285                 ValidateNCName( localName );
286             }
287             writer.WriteQualifiedName( localName, ns );
288         }
289
290
291 //
292 //  Private methods
293 //
294         private void CheckCharacters( string str ) {
295             XmlConvert.VerifyCharData( str, ExceptionType.ArgumentException );
296         }
297
298         private void CheckCharacters( char[] data, int offset, int len ) {
299             XmlConvert.VerifyCharData( data, offset, len, ExceptionType.ArgumentException );
300         }
301
302         private void ValidateNCName( string ncname ) {
303             if ( ncname.Length == 0 ) {
304                 throw new ArgumentException( Res.GetString( Res.Xml_EmptyName ) );
305             }
306             int len = ValidateNames.ParseNCName( ncname, 0 );
307             if ( len != ncname.Length ) {
308                 throw new ArgumentException(Res.GetString(len == 0 ? Res.Xml_BadStartNameChar : Res.Xml_BadNameChar, XmlException.BuildCharExceptionArgs(ncname, len)));
309             }
310         }
311
312         private void ValidateQName( string name ) {
313             if ( name.Length == 0 ) {
314                 throw new ArgumentException( Res.GetString( Res.Xml_EmptyName ) );
315             }
316             int colonPos;
317             int len = ValidateNames.ParseQName( name, 0, out colonPos );
318             if ( len != name.Length ) {
319                 string res = ( len == 0 || ( colonPos > -1 && len == colonPos + 1 ) ) ? Res.Xml_BadStartNameChar : Res.Xml_BadNameChar;
320                 throw new ArgumentException( Res.GetString( res, XmlException.BuildCharExceptionArgs( name, len ) ) );
321             }
322         }
323
324         private string ReplaceNewLines( string str ) {
325             if ( str == null ) {
326                 return null;
327             }
328
329             StringBuilder sb = null;
330             int start = 0;
331             int i;
332             for ( i = 0; i < str.Length; i++ ) {
333                 char ch;
334                 if ( ( ch = str[i] ) >= 0x20 ) {
335                     continue;
336                 }
337                 if ( ch == '\n' ) {
338                     if ( newLineChars == "\n" ) {
339                         continue;
340                     }
341                     if ( sb == null ) {
342                         sb = new StringBuilder( str.Length + 5 );
343                     }
344                     sb.Append( str, start, i - start );
345                 }
346                 else if ( ch == '\r' ) {
347                     if ( i + 1 < str.Length && str[i+1] == '\n' ) {
348                         if ( newLineChars == "\r\n" ) {
349                             i++;
350                             continue;
351                         }
352                         if ( sb == null ) {
353                             sb = new StringBuilder( str.Length  + 5 );
354                         }
355                         sb.Append( str, start, i - start );
356                         i++;
357                     }
358                     else {
359                         if ( newLineChars == "\r" ) {
360                             continue;
361                         }
362                         if ( sb == null ) {
363                             sb = new StringBuilder( str.Length + 5 );
364                         }
365                         sb.Append( str, start, i - start );
366                     }
367                 }
368                 else {
369                     continue; 
370                 }
371                 sb.Append( newLineChars );
372                 start = i + 1;
373             }
374
375             if ( sb == null ) {
376                 return str;
377             }
378             else {
379                 sb.Append( str, start, i - start );
380                 return sb.ToString();
381             }
382         }
383
384         private string ReplaceNewLines( char[] data, int offset, int len ) {
385             if ( data == null ) {
386                 return null;
387             }
388
389             StringBuilder sb = null;
390             int start = offset;
391             int endPos = offset + len;
392             int i;
393             for ( i = offset; i < endPos; i++ ) {
394                 char ch;
395                 if ( ( ch = data[i] ) >= 0x20 ) {
396                     continue;
397                 }
398                 if ( ch == '\n' ) {
399                     if ( newLineChars == "\n" ) {
400                         continue;
401                     }
402                     if ( sb == null ) {
403                         sb = new StringBuilder( len + 5 );
404                     }
405                     sb.Append( data, start, i - start );
406                 }
407                 else if ( ch == '\r' ) {
408                     if ( i + 1 < endPos && data[i+1] == '\n' ) {
409                         if ( newLineChars == "\r\n" ) {
410                             i++;
411                             continue;
412                         }
413                         if ( sb == null ) {
414                             sb = new StringBuilder( len + 5 );
415                         }
416                         sb.Append( data, start, i - start );
417                         i++;
418                     }
419                     else {
420                         if ( newLineChars == "\r" ) {
421                             continue;
422                         }
423                         if ( sb == null ) {
424                             sb = new StringBuilder( len + 5 );
425                         }
426                         sb.Append( data, start, i - start );
427                     }
428                 }
429                 else {
430                     continue; 
431                 }
432                 sb.Append( newLineChars );
433                 start = i + 1;
434             }
435
436             if ( sb == null ) {
437                 return null;
438             }
439             else {
440                 sb.Append( data, start, i - start );
441                 return sb.ToString();
442             }
443         }
444
445         // Interleave 2 adjacent invalid chars with a space. This is used for fixing invalid values of comments and PIs. 
446         // Any "--" in comment must be replaced with "- -" and any "-" at the end must be appended with " ".
447         // Any "?>" in PI value must be replaced with "? >". 
448         // This code has a 
449         private string InterleaveInvalidChars( string text, char invChar1, char invChar2 ) {
450             StringBuilder sb = null;
451             int start = 0;
452             int i;
453             for ( i = 0; i < text.Length; i++ ) {
454                 if ( text[i] != invChar2 ) {
455                     continue;
456                 }
457                 if ( i > 0 && text[i-1] == invChar1 ) {
458                     if ( sb == null ) {
459                         sb = new StringBuilder( text.Length + 5 );
460                     }
461                     sb.Append( text, start, i - start );
462                     sb.Append( ' ' );
463                     start = i;
464                 }
465             }
466
467             // check last char & return
468             if ( sb == null ) {
469                 return (i == 0 || text[i - 1] != invChar1) ? text : (text + ' ');
470             }
471             else {
472                 sb.Append( text, start, i - start );
473                 if (i > 0 && text[i - 1] == invChar1) {
474                     sb.Append(' ');
475                 }
476                 return sb.ToString();
477             }
478         }
479
480     }
481 }
482