// -*- 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 IFormatProvide / 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 { const int defaultCapacity = 16; private int sCapacity; private int sLength; private char[] sString; public StringBuilder() { // The MS Implementation uses the default // capacity for a StringBuilder. The spec // says it's up to the implementer, but // we'll do it the MS way just in case. sString = new char[ defaultCapacity ]; sCapacity = defaultCapacity; sLength = 0; } public StringBuilder( int capacity ) { if( capacity < defaultCapacity ) { // The spec says that the capacity // has to be at least the default capacity capacity = defaultCapacity; } sString = new char[capacity]; sCapacity = capacity; sLength = 0; } public StringBuilder( string str ) { if( str.Length < defaultCapacity ) { char[] tString = str.ToCharArray(); sString = new char[ defaultCapacity ]; Array.Copy( tString, sString, str.Length ); sLength = str.Length; sCapacity = defaultCapacity; } else { sString = str.ToCharArray(); sCapacity = sString.Length; sLength = sString.Length; } } 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, 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; } } } }