// -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- // // System.Text.StringBuilder // // Author: Marcin Szczepanski (marcins@zipworld.com.au) // // TODO: Implement the AppendFormat methods. Wasn't sure how // best to do this at this early stage, might want to see // how the String class and the IFormatProvider / IFormattable interfaces // pan out first. // // TODO: Make sure the coding complies to the ECMA draft, there's some // variable names that probably don't (like sString) // namespace System.Text { public sealed class StringBuilder { private const int defaultCapacity = 16; private int sCapacity; private int sLength; private char[] sString; private int sMaxCapacity = Int32.MaxValue; public StringBuilder(string str, int startIndex, int length, int capacity) { // the capacity must be at least as big as the default capacity // LAMESPEC: what to do if capacity is too small to hold the substring? // For now, truncate the substring to "capacity" characters sCapacity = Math.Max(capacity, defaultCapacity); sString = new char[sCapacity]; // LAMESPEC: what to do if startIndex is too big? Throw an exception? Which one? // For now, if the startIndex is beyond the end of the string, create an empty StringBuilder // Also, if str is null, then create an empty StringBuilder if (null == str || startIndex > str.Length - 1) { sLength = 0; } else { // LAMESPEC: what if the length specified would take us past the end of the string? // For now, copy to the end of the string // First find out how many characters we can actually copy sLength = Math.Min(length, str.Length - startIndex); // Then limit the length to the capacity if necessary. See LAMESPEC above. sLength = Math.Min(sLength, capacity); } // if the length is not going to be zero, then we have to copy some characters if (sLength > 0) { // Copy the correct number of characters into the internal array char[] tString = str.ToCharArray(startIndex, sLength); Array.Copy( tString, sString, sLength); } } public StringBuilder() : this(null, 0, 0, 0) {} public StringBuilder( int capacity ) : this(null, 0, 0, capacity) {} public StringBuilder( int capacity, int maxCapacity ) : this(null, 0, 0, capacity) { sMaxCapacity = maxCapacity; } public StringBuilder( string str ) : this(str, 0, str.Length, str.Length) {} public StringBuilder( string str, int capacity) : this(str, 0, str.Length, capacity) {} public int MaxCapacity { get { // TODO: Need to look at the memory of the system to return a useful value here return sMaxCapacity; } } public int Capacity { get { return sCapacity; } set { if( value < sLength ) { throw new ArgumentException( "Capacity must be > length" ); } else { char[] tString = new char[value]; Array.Copy( sString, tString, sLength ); sString = tString; sCapacity = sString.Length; } } } public int Length { get { return sLength; } set { if( value < 0 || value > Int32.MaxValue) { throw new ArgumentOutOfRangeException(); } else { if( value < sLength ) { // Truncate current string at value // LAMESPEC: The spec is unclear as to what to do // with the capacity when truncating the string. // // Don't change the capacity, as this is what // the MS implementation does. sLength = value; } else { // Expand the capacity to the new length and // pad the string with spaces. // LAMESPEC: The spec says to put the spaces on the // left of the string however the MS implementation // puts them on the right. We'll do that for // compatibility (!) char[] tString = new char[ value ]; int padLength = value - sLength; string padding = new String( ' ', padLength ); Array.Copy( sString, tString, sLength ); Array.Copy( padding.ToCharArray(), 0, tString, sLength, padLength ); sString = tString; sLength = sString.Length; sCapacity = value; } } } } public char this[ int index ] { get { if( index >= sLength || index < 0 ) { throw new IndexOutOfRangeException(); } return sString[ index ]; } set { if( index >= sLength || index < 0 ) { throw new IndexOutOfRangeException(); } sString[ index ] = value; } } public override string ToString() { return ToString(0, sLength); } public string ToString( int startIndex, int length ) { if( startIndex < 0 || length < 0 || startIndex + length > sLength ) { throw new ArgumentOutOfRangeException(); } return new String( sString, startIndex, length ); } public int EnsureCapacity( int capacity ) { if( capacity < 0 ) { throw new ArgumentOutOfRangeException( "Capacity must be greater than 0." ); } if( capacity <= sCapacity ) { return sCapacity; } else { Capacity = capacity; return sCapacity; } } public bool Equals( StringBuilder sb ) { if( this.ToString() == sb.ToString() ) { return true; } else { return false; } } public StringBuilder Remove( int startIndex, int length ) { if( startIndex < 0 || length < 0 || startIndex + length > sLength ) { throw new ArgumentOutOfRangeException(); } // Copy everything after the 'removed' part to the start // of the removed part and truncate the sLength Array.Copy( sString, startIndex + length, sString, startIndex, length ); sLength -= length; return this; } public StringBuilder Replace( char oldChar, char newChar ) { return Replace( oldChar, newChar, 0, sLength); } public StringBuilder Replace( char oldChar, char newChar, int startIndex, int count ) { if( startIndex + count > sLength || startIndex < 0 || count < 0 ) { throw new ArgumentOutOfRangeException(); } for( int replaceIterate = startIndex; replaceIterate < startIndex + count; replaceIterate++ ) { if( this[replaceIterate] == oldChar ) { this[replaceIterate] = newChar; } } return this; } public StringBuilder Replace( string oldValue, string newValue ) { return Replace( oldValue, newValue, 0, sLength ); } public StringBuilder Replace( string oldValue, string newValue, int startIndex, int count ) { string startString = this.ToString(); StringBuilder newStringB = new StringBuilder(); string newString; if( oldValue == null ) { throw new ArgumentNullException( "The old value cannot be null."); } if( startIndex < 0 || count < 0 || startIndex + count > sLength ) { throw new ArgumentOutOfRangeException(); } if( oldValue.Length == 0 ) { throw new ArgumentException( "The old value cannot be zero length."); } int nextIndex = startIndex; // Where to start the next search int lastIndex = nextIndex; // Where the last search finished while( nextIndex != -1 ) { nextIndex = startString.IndexOf( oldValue, lastIndex); if( nextIndex != -1 ) { // The MS implementation won't replace a substring // if that substring goes over the "count" // boundary, so we'll make sure the behaviour // here is the same. if( nextIndex + oldValue.Length <= startIndex + count ) { // Add everything to the left of the old // string newStringB.Append( startString.Substring( lastIndex, nextIndex - lastIndex ) ); // Add the replacement string newStringB.Append( newValue ); // Set the next start point to the // end of the last match lastIndex = nextIndex + oldValue.Length; } else { // We're past the "count" we're supposed to replace within nextIndex = -1; newStringB.Append( startString.Substring( lastIndex ) ); } } else { // Append everything left over newStringB.Append( startString.Substring( lastIndex ) ); } } newString = newStringB.ToString(); EnsureCapacity( newString.Length ); sString = newString.ToCharArray(); sLength = newString.Length; return this; } /* The Append Methods */ // TODO: Currently most of these methods convert the // parameter to a CharArray (via a String) and then pass // it to Append( char[] ). There might be a faster way // of doing this, but it's probably adequate and anything else // would make it too messy. // // As an example, a sample test run of appending a 100 character // string to the StringBuilder, and loooping this 50,000 times // results in an elapsed time of 2.4s using the MS StringBuilder // and 2.7s using this StringBuilder. Note that this results // in a 5 million character string. I believe MS uses a lot // of "native" DLLs for the "meat" of the base classes. public StringBuilder Append( char[] value ) { if( sLength + value.Length > sCapacity ) { // Need more capacity, double the capacity StringBuilder // and make sure we have at least enough for the value // if that's going to go over double. Capacity = value.Length + ( sCapacity + sCapacity); } Array.Copy( value, 0, sString, sLength, value.Length ); sLength += value.Length; return this; } public StringBuilder Append( string value ) { if( value != null ) { return Append( value.ToCharArray() ); } else { return null; } } public StringBuilder Append( bool value ) { return Append( value.ToString().ToCharArray() ); } public StringBuilder Append( byte value ) { return Append( value.ToString().ToCharArray() ); } public StringBuilder Append( int index, char value) { char[] appendChar = new char[1]; appendChar[0] = value; return Append( appendChar ); } public StringBuilder Append( decimal value ) { return Append( value.ToString().ToCharArray() ); } public StringBuilder Append( double value ) { return Append( value.ToString().ToCharArray() ); } public StringBuilder Append( short value ) { return Append( value.ToString().ToCharArray() ); } public StringBuilder Append( int value ) { return Append( value.ToString().ToCharArray() ); } public StringBuilder Append( long value ) { return Append( value.ToString().ToCharArray() ); } public StringBuilder Append( object value ) { return Append( value.ToString().ToCharArray() ); } public StringBuilder Append( sbyte value ) { return Append( value.ToString().ToCharArray() ); } public StringBuilder Append( float value ) { return Append( value.ToString().ToCharArray() ); } public StringBuilder Append( ushort value ) { return Append( value.ToString().ToCharArray() ); } public StringBuilder Append( uint value ) { return Append( value.ToString().ToCharArray() ); } public StringBuilder Append( ulong value ) { return Append( value.ToString().ToCharArray() ); } public StringBuilder Append( char value ) { return Append (value, 1); } public StringBuilder Append( char value, int repeatCount ) { if( repeatCount < 0 ) { throw new ArgumentOutOfRangeException(); } return Append( new String( value, repeatCount) ); } public StringBuilder Append( char[] value, int startIndex, int charCount ) { if( (charCount < 0 || startIndex < 0) || ( charCount + startIndex > value.Length ) ) { throw new ArgumentOutOfRangeException(); } if( value == null ) { if( !(startIndex == 0 && charCount == 0) ) { throw new ArgumentNullException(); } else { return this; } } else { char[] appendChars = new char[ charCount ]; Array.Copy( value, startIndex, appendChars, 0, charCount ); return Append( appendChars ); } } public StringBuilder Append( string value, int startIndex, int count ) { if( (count < 0 || startIndex < 0) || ( startIndex + count > value.Length ) ) { throw new ArgumentOutOfRangeException(); } return Append( value.Substring( startIndex, count ).ToCharArray() ); } public StringBuilder AppendFormat( string format, object arg0 ) { // TODO: Implement return this; } public StringBuilder AppendFormat( string format, params object[] args ) { // TODO: Implement return this; } public StringBuilder AppendFormat( IFormatProvider provider, string format, params object[] args ) { // TODO: Implement return this; } public StringBuilder AppendFormat( string format, object arg0, object arg1 ) { // TODO: Implement; return this; } public StringBuilder AppendFormat( string format, object arg0, object arg1, object arg2 ) { // TODO Implement return this; } /* The Insert Functions */ // Similarly to the Append functions, get everything down to a CharArray // and insert that. public StringBuilder Insert( int index, char[] value ) { if( index > sLength || index < 0) { throw new ArgumentOutOfRangeException(); } if( value == null || value.Length == 0 ) { return this; } else { // Check we have the capacity to insert this array if( sCapacity < sLength + value.Length ) { Capacity = value.Length + ( sCapacity + sCapacity ); } // Move everything to the right of the insert point across Array.Copy( sString, index, sString, index + value.Length, sLength - index); // Copy in stuff from the insert buffer Array.Copy( value, 0, sString, index, value.Length ); sLength += value.Length; return this; } } public StringBuilder Insert( int index, string value ) { return Insert( index, value.ToCharArray() ); } public StringBuilder Insert( int index, bool value ) { return Insert( index, value.ToString().ToCharArray() ); } public StringBuilder Insert( int index, byte value ) { return Insert( index, value.ToString().ToCharArray() ); } public StringBuilder Insert( int index, char value) { char[] insertChar = new char[1]; insertChar[0] = value; return Insert( index, insertChar ); } public StringBuilder Insert( int index, decimal value ) { return Insert( index, value.ToString().ToCharArray() ); } public StringBuilder Insert( int index, double value ) { return Insert( index, value.ToString().ToCharArray() ); } public StringBuilder Insert( int index, short value ) { return Insert( index, value.ToString().ToCharArray() ); } public StringBuilder Insert( int index, int value ) { return Insert( index, value.ToString().ToCharArray() ); } public StringBuilder Insert( int index, long value ) { return Insert( index, value.ToString().ToCharArray() ); } public StringBuilder Insert( int index, object value ) { return Insert( index, value.ToString().ToCharArray() ); } public StringBuilder Insert( int index, sbyte value ) { return Insert( index, value.ToString().ToCharArray() ); } public StringBuilder Insert( int index, float value ) { return Insert( index, value.ToString().ToCharArray() ); } public StringBuilder Insert( int index, ushort value ) { return Insert( index, value.ToString().ToCharArray() ); } public StringBuilder Insert( int index, uint value ) { return Insert( index, value.ToString().ToCharArray() ); } public StringBuilder Insert( int index, ulong value ) { return Insert( index, value.ToString().ToCharArray() ); } public StringBuilder Insert( int index, string value, int count ) { if ( count < 0 ) { throw new ArgumentOutOfRangeException(); } if( value != null ) { if( value != "" ) { for( int insertCount = 0; insertCount < count; insertCount++ ) { Insert( index, value.ToCharArray() ); } } } return this; } public StringBuilder Insert( int index, char[] value, int startIndex, int charCount ) { if( value != null ) { if( charCount < 0 || startIndex < 0 || startIndex + charCount > value.Length ) { throw new ArgumentOutOfRangeException(); } char[] insertChars = new char[ charCount ]; Array.Copy( value, startIndex, insertChars, 0, charCount ); return Insert( index, insertChars ); } else { return this; } } } }