3 //------------------------------------------------------------------------------
4 // <copyright file="XmlCharCheckingWriter.cs" company="Microsoft">
5 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // <owner current="true" primary="true">[....]</owner>
8 //------------------------------------------------------------------------------
34 using System.Xml.Schema;
35 using System.Collections;
36 using System.Diagnostics;
43 namespace System.Xml {
46 // XmlCharCheckingWriter
48 internal partial class XmlCharCheckingWriter : XmlWrappingWriter {
59 XmlCharType xmlCharType;
63 internal XmlCharCheckingWriter( XmlWriter baseWriter, bool checkValues, bool checkNames, bool replaceNewLines, string newLineChars )
64 : base( baseWriter ) {
66 Debug.Assert( checkValues || replaceNewLines );
67 this.checkValues = checkValues;
68 this.checkNames = checkNames;
69 this.replaceNewLines = replaceNewLines;
70 this.newLineChars = newLineChars;
73 xmlCharType = XmlCharType.Instance;
78 // XmlWriter implementation
81 public override XmlWriterSettings Settings {
83 XmlWriterSettings s = base.writer.Settings;
84 s = ( s != null) ? (XmlWriterSettings)s.Clone() : new XmlWriterSettings();
87 s.CheckCharacters = true;
89 if ( replaceNewLines ) {
90 s.NewLineHandling = NewLineHandling.Replace;
91 s.NewLineChars = newLineChars;
99 public override void WriteDocType( string name, string pubid, string sysid, string subset ) {
101 ValidateQName( name );
104 if ( pubid != null ) {
106 if ( ( i = xmlCharType.IsPublicId( pubid ) ) >= 0 ) {
107 throw XmlConvert.CreateInvalidCharException( pubid, i );
110 if ( sysid != null ) {
111 CheckCharacters( sysid );
113 if ( subset != null ) {
114 CheckCharacters( subset );
117 if ( replaceNewLines ) {
118 sysid = ReplaceNewLines( sysid );
119 pubid = ReplaceNewLines( pubid );
120 subset = ReplaceNewLines( subset );
122 writer.WriteDocType( name, pubid, sysid, subset );
125 public override void WriteStartElement( string prefix, string localName, string ns ) {
127 if ( localName == null || localName.Length == 0 ) {
128 throw new ArgumentException( Res.GetString( Res.Xml_EmptyLocalName ) );
130 ValidateNCName( localName );
132 if ( prefix != null && prefix.Length > 0 ) {
133 ValidateNCName( prefix );
136 writer.WriteStartElement( prefix, localName, ns );
139 public override void WriteStartAttribute( string prefix, string localName, string ns ) {
141 if ( localName == null || localName.Length == 0 ) {
142 throw new ArgumentException( Res.GetString( Res.Xml_EmptyLocalName ) );
144 ValidateNCName( localName );
146 if ( prefix != null && prefix.Length > 0 ) {
147 ValidateNCName( prefix );
150 writer.WriteStartAttribute( prefix, localName, ns );
153 public override void WriteCData( string text ) {
154 if ( text != null ) {
156 CheckCharacters( text );
158 if ( replaceNewLines ) {
159 text = ReplaceNewLines( text );
162 while ( ( i = text.IndexOf( "]]>", StringComparison.Ordinal ) ) >= 0 ) {
163 writer.WriteCData( text.Substring( 0, i + 2 ) );
164 text = text.Substring( i + 2 );
167 writer.WriteCData( text );
170 public override void WriteComment( string text ) {
171 if ( text != null ) {
173 CheckCharacters( text );
174 text = InterleaveInvalidChars( text, '-', '-' );
176 if ( replaceNewLines ) {
177 text = ReplaceNewLines( text );
180 writer.WriteComment( text );
183 public override void WriteProcessingInstruction( string name, string text ) {
185 ValidateNCName( name );
187 if ( text != null ) {
189 CheckCharacters( text );
190 text = InterleaveInvalidChars( text, '?', '>' );
192 if ( replaceNewLines ) {
193 text = ReplaceNewLines( text );
196 writer.WriteProcessingInstruction( name, text );
199 public override void WriteEntityRef( string name ) {
201 ValidateQName( name );
203 writer.WriteEntityRef( name );
206 public override void WriteWhitespace( string ws ) {
210 // "checkNames" is intentional here; if false, the whitespaces are checked in XmlWellformedWriter
213 if ( ( i = xmlCharType.IsOnlyWhitespaceWithPos( ws ) ) != -1 ) {
214 throw new ArgumentException( Res.GetString( Res.Xml_InvalidWhitespaceCharacter, XmlException.BuildCharExceptionArgs( ws, i ) ) );
217 if ( replaceNewLines ) {
218 ws = ReplaceNewLines( ws );
220 writer.WriteWhitespace( ws );
223 public override void WriteString( string text ) {
224 if ( text != null ) {
226 CheckCharacters( text );
228 if ( replaceNewLines && WriteState != WriteState.Attribute ) {
229 text = ReplaceNewLines( text );
232 writer.WriteString( text );
235 public override void WriteSurrogateCharEntity( char lowChar, char highChar ) {
236 writer.WriteSurrogateCharEntity( lowChar, highChar );
239 public override void WriteChars( char[] buffer, int index, int count ) {
240 if (buffer == null) {
241 throw new ArgumentNullException("buffer");
244 throw new ArgumentOutOfRangeException("index");
247 throw new ArgumentOutOfRangeException("count");
249 if (count > buffer.Length - index) {
250 throw new ArgumentOutOfRangeException("count");
254 CheckCharacters( buffer, index, count );
256 if ( replaceNewLines && WriteState != WriteState.Attribute ) {
257 string text = ReplaceNewLines( buffer, index, count );
258 if ( text != null ) {
263 writer.WriteChars( buffer, index, count );
266 public override void WriteNmToken( string name ) {
268 if ( name == null || name.Length == 0 ) {
269 throw new ArgumentException( Res.GetString( Res.Xml_EmptyName ) );
271 XmlConvert.VerifyNMTOKEN( name );
273 writer.WriteNmToken( name );
276 public override void WriteName( string name ) {
278 XmlConvert.VerifyQName( name, ExceptionType.XmlException );
280 writer.WriteName( name );
283 public override void WriteQualifiedName( string localName, string ns ) {
285 ValidateNCName( localName );
287 writer.WriteQualifiedName( localName, ns );
294 private void CheckCharacters( string str ) {
295 XmlConvert.VerifyCharData( str, ExceptionType.ArgumentException );
298 private void CheckCharacters( char[] data, int offset, int len ) {
299 XmlConvert.VerifyCharData( data, offset, len, ExceptionType.ArgumentException );
302 private void ValidateNCName( string ncname ) {
303 if ( ncname.Length == 0 ) {
304 throw new ArgumentException( Res.GetString( Res.Xml_EmptyName ) );
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)));
312 private void ValidateQName( string name ) {
313 if ( name.Length == 0 ) {
314 throw new ArgumentException( Res.GetString( Res.Xml_EmptyName ) );
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 ) ) );
324 private string ReplaceNewLines( string str ) {
329 StringBuilder sb = null;
332 for ( i = 0; i < str.Length; i++ ) {
334 if ( ( ch = str[i] ) >= 0x20 ) {
338 if ( newLineChars == "\n" ) {
342 sb = new StringBuilder( str.Length + 5 );
344 sb.Append( str, start, i - start );
346 else if ( ch == '\r' ) {
347 if ( i + 1 < str.Length && str[i+1] == '\n' ) {
348 if ( newLineChars == "\r\n" ) {
353 sb = new StringBuilder( str.Length + 5 );
355 sb.Append( str, start, i - start );
359 if ( newLineChars == "\r" ) {
363 sb = new StringBuilder( str.Length + 5 );
365 sb.Append( str, start, i - start );
371 sb.Append( newLineChars );
379 sb.Append( str, start, i - start );
380 return sb.ToString();
384 private string ReplaceNewLines( char[] data, int offset, int len ) {
385 if ( data == null ) {
389 StringBuilder sb = null;
391 int endPos = offset + len;
393 for ( i = offset; i < endPos; i++ ) {
395 if ( ( ch = data[i] ) >= 0x20 ) {
399 if ( newLineChars == "\n" ) {
403 sb = new StringBuilder( len + 5 );
405 sb.Append( data, start, i - start );
407 else if ( ch == '\r' ) {
408 if ( i + 1 < endPos && data[i+1] == '\n' ) {
409 if ( newLineChars == "\r\n" ) {
414 sb = new StringBuilder( len + 5 );
416 sb.Append( data, start, i - start );
420 if ( newLineChars == "\r" ) {
424 sb = new StringBuilder( len + 5 );
426 sb.Append( data, start, i - start );
432 sb.Append( newLineChars );
440 sb.Append( data, start, i - start );
441 return sb.ToString();
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 bug SQL BU Defect Tracking #480848, which was triaged as Won't Fix because it is a breaking change
449 private string InterleaveInvalidChars( string text, char invChar1, char invChar2 ) {
450 StringBuilder sb = null;
453 for ( i = 0; i < text.Length; i++ ) {
454 if ( text[i] != invChar2 ) {
457 if ( i > 0 && text[i-1] == invChar1 ) {
459 sb = new StringBuilder( text.Length + 5 );
461 sb.Append( text, start, i - start );
467 // check last char & return
469 return (i == 0 || text[i - 1] != invChar1) ? text : (text + ' ');
472 sb.Append( text, start, i - start );
473 if (i > 0 && text[i - 1] == invChar1) {
476 return sb.ToString();