1 // -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
\r
3 // System.Text.StringBuilder
\r
5 // Author: Marcin Szczepanski (marcins@zipworld.com.au)
\r
7 // TODO: Implement the AppendFormat methods. Wasn't sure how
\r
8 // best to do this at this early stage, might want to see
\r
9 // how the String class and the IFormatProvider / IFormattable interfaces
\r
12 // TODO: Make sure the coding complies to the ECMA draft, there's some
\r
13 // variable names that probably don't (like sString)
\r
15 namespace System.Text {
\r
16 public sealed class StringBuilder {
\r
18 private const int defaultCapacity = 16;
\r
20 private int sCapacity;
\r
21 private int sLength;
\r
22 private char[] sString;
\r
23 private int sMaxCapacity = Int32.MaxValue;
\r
25 public StringBuilder(string value, int startIndex, int length, int capacity) {
\r
26 // first, check the parameters and throw appropriate exceptions if needed
\r
28 throw new System.ArgumentNullException("value");
\r
31 // make sure startIndex is zero or positive
\r
32 if(startIndex < 0) {
\r
33 throw new System.ArgumentOutOfRangeException("startIndex", startIndex, "StartIndex cannot be less than zero.");
\r
36 // make sure length is zero or positive
\r
38 throw new System.ArgumentOutOfRangeException("length", length, "Length cannot be less than zero.");
\r
41 // make sure startIndex and length give a valid substring of value
\r
42 if(startIndex + (length -1) > (value.Length - 1) ) {
\r
43 throw new System.ArgumentOutOfRangeException("startIndex", startIndex, "StartIndex and length must refer to a location within the string.");
\r
46 // the capacity must be at least as big as the default capacity
\r
47 sCapacity = Math.Max(capacity, defaultCapacity);
\r
49 // LAMESPEC: what to do if capacity is too small to hold the substring?
\r
50 // Like the MS implementation, double the capacity until it is large enough
\r
51 while (sCapacity < length) {
\r
52 // However, take care not to double if that would make the number
\r
53 // larger than what an int can hold
\r
54 if (sCapacity <= Int32.MaxValue / 2) {
\r
58 sCapacity = Int32.MaxValue;
\r
62 sString = new char[sCapacity];
\r
65 // if the length is not zero, then we have to copy some characters
\r
67 // Copy the correct number of characters into the internal array
\r
68 char[] tString = value.ToCharArray(startIndex, sLength);
\r
69 Array.Copy( tString, sString, sLength);
\r
73 public StringBuilder() : this(String.Empty, 0, 0, 0) {}
\r
75 public StringBuilder( int capacity ) : this("", 0, 0, capacity) {}
\r
77 public StringBuilder( int capacity, int maxCapacity ) : this("", 0, 0, capacity) {
\r
78 if(capacity > maxCapacity) {
\r
79 throw new System.ArgumentOutOfRangeException("capacity", "Capacity exceeds maximum capacity.");
\r
81 sMaxCapacity = maxCapacity;
\r
84 public StringBuilder( string value ) : this(value, 0, value == null ? 0 : value.Length, value == null? 0 : value.Length) {
\r
87 public StringBuilder( string value, int capacity) : this(value, 0, value.Length, capacity) {}
\r
89 public int MaxCapacity {
\r
91 // TODO: Need to look at the memory of the system to return a useful value here
\r
92 return sMaxCapacity;
\r
96 public int Capacity {
\r
102 if( value < sLength ) {
\r
103 throw new ArgumentException( "Capacity must be > length" );
\r
105 char[] tString = new char[value];
\r
106 Array.Copy( sString, tString, sLength );
\r
108 sCapacity = sString.Length;
\r
114 public int Length {
\r
120 if( value < 0 || value > MaxCapacity) {
\r
121 throw new ArgumentOutOfRangeException();
\r
123 if( value < sLength ) {
\r
124 // Truncate current string at value
\r
126 // LAMESPEC: The spec is unclear as to what to do
\r
127 // with the capacity when truncating the string.
\r
129 // Don't change the capacity, as this is what
\r
130 // the MS implementation does.
\r
134 // Expand the capacity to the new length and
\r
135 // pad the string with spaces.
\r
137 // LAMESPEC: The spec says to put the spaces on the
\r
138 // left of the string however the MS implementation
\r
139 // puts them on the right. We'll do that for
\r
140 // compatibility (!)
\r
142 char[] tString = new char[ value ];
\r
143 int padLength = value - sLength;
\r
145 string padding = new String( ' ', padLength );
\r
146 Array.Copy( sString, tString, sLength );
\r
147 Array.Copy( padding.ToCharArray(), 0, tString, sLength, padLength );
\r
149 sLength = sString.Length;
\r
156 public char this[ int index ] {
\r
159 if( index >= sLength || index < 0 ) {
\r
160 throw new IndexOutOfRangeException();
\r
162 return sString[ index ];
\r
166 if( index >= sLength || index < 0 ) {
\r
167 throw new IndexOutOfRangeException();
\r
169 sString[ index ] = value;
\r
173 public override string ToString() {
\r
174 return ToString(0, sLength);
\r
177 public string ToString( int startIndex, int length ) {
\r
178 if( startIndex < 0 || length < 0 || startIndex + length > sLength ) {
\r
179 throw new ArgumentOutOfRangeException();
\r
182 return new String( sString, startIndex, length );
\r
185 public int EnsureCapacity( int capacity ) {
\r
186 if( capacity < 0 ) {
\r
187 throw new ArgumentOutOfRangeException(
\r
188 "Capacity must be greater than 0." );
\r
191 if( capacity <= sCapacity ) {
\r
194 Capacity = capacity;
\r
199 public bool Equals( StringBuilder sb ) {
\r
200 if( this.ToString() == sb.ToString() ) {
\r
207 public StringBuilder Remove( int startIndex, int length ) {
\r
208 if( startIndex < 0 || length < 0 || startIndex + length > sLength ) {
\r
209 throw new ArgumentOutOfRangeException();
\r
212 // Copy everything after the 'removed' part to the start
\r
213 // of the removed part and truncate the sLength
\r
215 Array.Copy( sString, startIndex + length, sString,
\r
216 startIndex, length );
\r
222 public StringBuilder Replace( char oldChar, char newChar ) {
\r
224 return Replace( oldChar, newChar, 0, sLength);
\r
227 public StringBuilder Replace( char oldChar, char newChar, int startIndex, int count ) {
\r
228 if( startIndex + count > sLength || startIndex < 0 || count < 0 ) {
\r
229 throw new ArgumentOutOfRangeException();
\r
232 for( int replaceIterate = startIndex; replaceIterate < startIndex + count; replaceIterate++ ) {
\r
233 if( this[replaceIterate] == oldChar ) {
\r
234 this[replaceIterate] = newChar;
\r
241 public StringBuilder Replace( string oldValue, string newValue ) {
\r
242 return Replace( oldValue, newValue, 0, sLength );
\r
245 public StringBuilder Replace( string oldValue, string newValue, int startIndex, int count ) {
\r
246 string startString = this.ToString();
\r
247 StringBuilder newStringB = new StringBuilder();
\r
250 if( oldValue == null ) {
\r
251 throw new ArgumentNullException(
\r
252 "The old value cannot be null.");
\r
255 if( startIndex < 0 || count < 0 || startIndex + count > sLength ) {
\r
256 throw new ArgumentOutOfRangeException();
\r
259 if( oldValue.Length == 0 ) {
\r
260 throw new ArgumentException(
\r
261 "The old value cannot be zero length.");
\r
264 int nextIndex = startIndex; // Where to start the next search
\r
265 int lastIndex = nextIndex; // Where the last search finished
\r
267 while( nextIndex != -1 ) {
\r
268 nextIndex = startString.IndexOf( oldValue, lastIndex);
\r
269 if( nextIndex != -1 ) {
\r
270 // The MS implementation won't replace a substring
\r
271 // if that substring goes over the "count"
\r
272 // boundary, so we'll make sure the behaviour
\r
273 // here is the same.
\r
275 if( nextIndex + oldValue.Length <= startIndex + count ) {
\r
277 // Add everything to the left of the old
\r
279 newStringB.Append( startString.Substring( lastIndex, nextIndex - lastIndex ) );
\r
281 // Add the replacement string
\r
282 newStringB.Append( newValue );
\r
284 // Set the next start point to the
\r
285 // end of the last match
\r
286 lastIndex = nextIndex + oldValue.Length;
\r
288 // We're past the "count" we're supposed to replace within
\r
290 newStringB.Append(
\r
291 startString.Substring( lastIndex ) );
\r
295 // Append everything left over
\r
296 newStringB.Append( startString.Substring( lastIndex ) );
\r
300 newString = newStringB.ToString();
\r
302 EnsureCapacity( newString.Length );
\r
303 sString = newString.ToCharArray();
\r
304 sLength = newString.Length;
\r
309 /* The Append Methods */
\r
311 // TODO: Currently most of these methods convert the
\r
312 // parameter to a CharArray (via a String) and then pass
\r
313 // it to Append( char[] ). There might be a faster way
\r
314 // of doing this, but it's probably adequate and anything else
\r
315 // would make it too messy.
\r
317 // As an example, a sample test run of appending a 100 character
\r
318 // string to the StringBuilder, and loooping this 50,000 times
\r
319 // results in an elapsed time of 2.4s using the MS StringBuilder
\r
320 // and 2.7s using this StringBuilder. Note that this results
\r
321 // in a 5 million character string. I believe MS uses a lot
\r
322 // of "native" DLLs for the "meat" of the base classes.
\r
325 public StringBuilder Append( char[] value ) {
\r
326 if( sLength + value.Length > sCapacity ) {
\r
327 // Need more capacity, double the capacity StringBuilder
\r
328 // and make sure we have at least enough for the value
\r
329 // if that's going to go over double.
\r
331 Capacity = value.Length + ( sCapacity + sCapacity);
\r
334 Array.Copy( value, 0, sString, sLength, value.Length );
\r
335 sLength += value.Length;
\r
340 public StringBuilder Append( string value ) {
\r
341 if( value != null ) {
\r
342 return Append( value.ToCharArray() );
\r
348 public StringBuilder Append( bool value ) {
\r
349 return Append( value.ToString().ToCharArray() );
\r
352 public StringBuilder Append( byte value ) {
\r
353 return Append( value.ToString().ToCharArray() );
\r
356 public StringBuilder Append( int index, char value) {
\r
357 char[] appendChar = new char[1];
\r
359 appendChar[0] = value;
\r
360 return Append( appendChar );
\r
364 public StringBuilder Append( decimal value ) {
\r
365 return Append( value.ToString().ToCharArray() );
\r
368 public StringBuilder Append( double value ) {
\r
369 return Append( value.ToString().ToCharArray() );
\r
372 public StringBuilder Append( short value ) {
\r
373 return Append( value.ToString().ToCharArray() );
\r
376 public StringBuilder Append( int value ) {
\r
377 return Append( value.ToString().ToCharArray() );
\r
380 public StringBuilder Append( long value ) {
\r
381 return Append( value.ToString().ToCharArray() );
\r
384 public StringBuilder Append( object value ) {
\r
385 return Append( value.ToString().ToCharArray() );
\r
388 [CLSCompliant(false)]
\r
389 public StringBuilder Append( sbyte value ) {
\r
390 return Append( value.ToString().ToCharArray() );
\r
393 public StringBuilder Append( float value ) {
\r
394 return Append( value.ToString().ToCharArray() );
\r
397 [CLSCompliant(false)]
\r
398 public StringBuilder Append( ushort value ) {
\r
399 return Append( value.ToString().ToCharArray() );
\r
402 [CLSCompliant(false)]
\r
403 public StringBuilder Append( uint value ) {
\r
404 return Append( value.ToString().ToCharArray() );
\r
407 [CLSCompliant(false)]
\r
408 public StringBuilder Append( ulong value ) {
\r
409 return Append( value.ToString().ToCharArray() );
\r
412 public StringBuilder Append( char value ) {
\r
413 return Append (value, 1);
\r
416 public StringBuilder Append( char value, int repeatCount ) {
\r
417 if( repeatCount < 0 ) {
\r
418 throw new ArgumentOutOfRangeException();
\r
421 return Append( new String( value, repeatCount) );
\r
424 public StringBuilder Append( char[] value, int startIndex, int charCount ) {
\r
426 if( (charCount < 0 || startIndex < 0) ||
\r
427 ( charCount + startIndex > value.Length ) ) {
\r
428 throw new ArgumentOutOfRangeException();
\r
431 if( value == null ) {
\r
432 if( !(startIndex == 0 && charCount == 0) ) {
\r
433 throw new ArgumentNullException();
\r
438 char[] appendChars = new char[ charCount ];
\r
440 Array.Copy( value, startIndex, appendChars, 0, charCount );
\r
441 return Append( appendChars );
\r
445 public StringBuilder Append( string value, int startIndex, int count ) {
\r
446 if( (count < 0 || startIndex < 0) ||
\r
447 ( startIndex + count > value.Length ) ) {
\r
448 throw new ArgumentOutOfRangeException();
\r
451 return Append( value.Substring( startIndex, count ).ToCharArray() );
\r
454 public StringBuilder AppendFormat( string format, object arg0 ) {
\r
459 public StringBuilder AppendFormat( string format, params object[] args ) {
\r
464 public StringBuilder AppendFormat( IFormatProvider provider, string format,
\r
465 params object[] args ) {
\r
470 public StringBuilder AppendFormat( string format, object arg0, object arg1 ) {
\r
471 // TODO: Implement;
\r
475 public StringBuilder AppendFormat( string format, object arg0, object arg1, object arg2 ) {
\r
480 /* The Insert Functions */
\r
482 // Similarly to the Append functions, get everything down to a CharArray
\r
483 // and insert that.
\r
485 public StringBuilder Insert( int index, char[] value ) {
\r
486 if( index > sLength || index < 0) {
\r
487 throw new ArgumentOutOfRangeException();
\r
490 if( value == null || value.Length == 0 ) {
\r
493 // Check we have the capacity to insert this array
\r
494 if( sCapacity < sLength + value.Length ) {
\r
495 Capacity = value.Length + ( sCapacity + sCapacity );
\r
498 // Move everything to the right of the insert point across
\r
499 Array.Copy( sString, index, sString, index + value.Length, sLength - index);
\r
501 // Copy in stuff from the insert buffer
\r
502 Array.Copy( value, 0, sString, index, value.Length );
\r
504 sLength += value.Length;
\r
509 public StringBuilder Insert( int index, string value ) {
\r
510 return Insert( index, value.ToCharArray() );
\r
513 public StringBuilder Insert( int index, bool value ) {
\r
514 return Insert( index, value.ToString().ToCharArray() );
\r
517 public StringBuilder Insert( int index, byte value ) {
\r
518 return Insert( index, value.ToString().ToCharArray() );
\r
521 public StringBuilder Insert( int index, char value) {
\r
522 char[] insertChar = new char[1];
\r
524 insertChar[0] = value;
\r
525 return Insert( index, insertChar );
\r
528 public StringBuilder Insert( int index, decimal value ) {
\r
529 return Insert( index, value.ToString().ToCharArray() );
\r
532 public StringBuilder Insert( int index, double value ) {
\r
533 return Insert( index, value.ToString().ToCharArray() );
\r
536 public StringBuilder Insert( int index, short value ) {
\r
537 return Insert( index, value.ToString().ToCharArray() );
\r
540 public StringBuilder Insert( int index, int value ) {
\r
541 return Insert( index, value.ToString().ToCharArray() );
\r
544 public StringBuilder Insert( int index, long value ) {
\r
545 return Insert( index, value.ToString().ToCharArray() );
\r
548 public StringBuilder Insert( int index, object value ) {
\r
549 return Insert( index, value.ToString().ToCharArray() );
\r
552 [CLSCompliant(false)]
\r
553 public StringBuilder Insert( int index, sbyte value ) {
\r
554 return Insert( index, value.ToString().ToCharArray() );
\r
557 public StringBuilder Insert( int index, float value ) {
\r
558 return Insert( index, value.ToString().ToCharArray() );
\r
561 [CLSCompliant(false)]
\r
562 public StringBuilder Insert( int index, ushort value ) {
\r
563 return Insert( index, value.ToString().ToCharArray() );
\r
566 [CLSCompliant(false)]
\r
567 public StringBuilder Insert( int index, uint value ) {
\r
568 return Insert( index, value.ToString().ToCharArray() );
\r
571 [CLSCompliant(false)]
\r
572 public StringBuilder Insert( int index, ulong value ) {
\r
573 return Insert( index, value.ToString().ToCharArray() );
\r
576 public StringBuilder Insert( int index, string value, int count ) {
\r
578 throw new ArgumentOutOfRangeException();
\r
581 if( value != null ) {
\r
582 if( value != "" ) {
\r
583 for( int insertCount = 0; insertCount < count;
\r
585 Insert( index, value.ToCharArray() );
\r
592 public StringBuilder Insert( int index, char[] value, int startIndex,
\r
595 if( value != null ) {
\r
596 if( charCount < 0 || startIndex < 0 || startIndex + charCount > value.Length ) {
\r
597 throw new ArgumentOutOfRangeException();
\r
600 char[] insertChars = new char[ charCount ];
\r
601 Array.Copy( value, startIndex, insertChars, 0, charCount );
\r
602 return Insert( index, insertChars );
\r