StringBuilder.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. // -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  2. //
  3. // System.Text.StringBuilder
  4. //
  5. // Author: Marcin Szczepanski ([email protected])
  6. //
  7. // TODO: Implement the AppendFormat methods. Wasn't sure how
  8. // best to do this at this early stage, might want to see
  9. // how the String class and the IFormatProvide / IFormattable interfaces
  10. // pan out first.
  11. //
  12. // TODO: Make sure the coding complies to the ECMA draft, there's some
  13. // variable names that probably don't (like sString)
  14. //
  15. namespace System.Text {
  16. public sealed class StringBuilder {
  17. const int defaultCapacity = 16;
  18. private int sCapacity;
  19. private int sLength;
  20. private char[] sString;
  21. public StringBuilder() {
  22. // The MS Implementation uses the default
  23. // capacity for a StringBuilder. The spec
  24. // says it's up to the implementer, but
  25. // we'll do it the MS way just in case.
  26. sString = new char[ defaultCapacity ];
  27. sCapacity = defaultCapacity;
  28. sLength = 0;
  29. }
  30. public StringBuilder( int capacity ) {
  31. if( capacity < defaultCapacity ) {
  32. // The spec says that the capacity
  33. // has to be at least the default capacity
  34. capacity = defaultCapacity;
  35. }
  36. sString = new char[capacity];
  37. sCapacity = capacity;
  38. sLength = 0;
  39. }
  40. public StringBuilder( string str ) {
  41. if( str.Length < defaultCapacity ) {
  42. char[] tString = str.ToCharArray();
  43. sString = new char[ defaultCapacity ];
  44. Array.Copy( tString, sString, str.Length );
  45. sLength = str.Length;
  46. sCapacity = defaultCapacity;
  47. } else {
  48. sString = str.ToCharArray();
  49. sCapacity = sString.Length;
  50. sLength = sString.Length;
  51. }
  52. }
  53. public int Capacity {
  54. get {
  55. return sCapacity;
  56. }
  57. set {
  58. if( value < sLength ) {
  59. throw new ArgumentException( "Capacity must be > length" );
  60. } else {
  61. char[] tString = new char[value];
  62. Array.Copy( sString, tString, sLength );
  63. sString = tString;
  64. sCapacity = sString.Length;
  65. }
  66. }
  67. }
  68. public int Length {
  69. get {
  70. return sLength;
  71. }
  72. set {
  73. if( value < 0 || value > Int32.MaxValue) {
  74. throw new ArgumentOutOfRangeException();
  75. } else {
  76. if( value < sLength ) {
  77. // Truncate current string at value
  78. // LAMESPEC: The spec is unclear as to what to do
  79. // with the capacity when truncating the string.
  80. //
  81. // Don't change the capacity, as this is what
  82. // the MS implementation does.
  83. sLength = value;
  84. } else {
  85. // Expand the capacity to the new length and
  86. // pad the string with spaces.
  87. // LAMESPEC: The spec says to put the spaces on the
  88. // left of the string however the MS implementation
  89. // puts them on the right. We'll do that for
  90. // compatibility (!)
  91. char[] tString = new char[ value ];
  92. int padLength = value - sLength;
  93. string padding = new String( ' ', padLength );
  94. Array.Copy( sString, tString, sLength );
  95. Array.Copy( padding.ToCharArray(), 0, tString, sLength, padLength );
  96. sString = tString;
  97. sLength = sString.Length;
  98. sCapacity = value;
  99. }
  100. }
  101. }
  102. }
  103. public char this[ int index ] {
  104. get {
  105. if( index >= sLength || index < 0 ) {
  106. throw new IndexOutOfRangeException();
  107. }
  108. return sString[ index ];
  109. }
  110. set {
  111. if( index >= sLength || index < 0 ) {
  112. throw new IndexOutOfRangeException();
  113. }
  114. sString[ index ] = value;
  115. }
  116. }
  117. public override string ToString() {
  118. return ToString(0, sLength);
  119. }
  120. public string ToString( int startIndex, int length ) {
  121. if( startIndex < 0 || length < 0 || startIndex + length > sLength ) {
  122. throw new ArgumentOutOfRangeException();
  123. }
  124. return new String( sString, startIndex, length );
  125. }
  126. public int EnsureCapacity( int capacity ) {
  127. if( capacity < 0 ) {
  128. throw new ArgumentOutOfRangeException(
  129. "Capacity must be greater than 0." );
  130. }
  131. if( capacity <= sCapacity ) {
  132. return sCapacity;
  133. } else {
  134. Capacity = capacity;
  135. return sCapacity;
  136. }
  137. }
  138. public bool Equals( StringBuilder sb ) {
  139. if( this.ToString() == sb.ToString() ) {
  140. return true;
  141. } else {
  142. return false;
  143. }
  144. }
  145. public StringBuilder Remove( int startIndex, int length ) {
  146. if( startIndex < 0 || length < 0 || startIndex + length > sLength ) {
  147. throw new ArgumentOutOfRangeException();
  148. }
  149. // Copy everything after the 'removed' part to the start
  150. // of the removed part and truncate the sLength
  151. Array.Copy( sString, startIndex + length, sString,
  152. startIndex, length );
  153. sLength -= length;
  154. return this;
  155. }
  156. public StringBuilder Replace( char oldChar, char newChar ) {
  157. return Replace( oldChar, newChar, 0, sLength);
  158. }
  159. public StringBuilder Replace( char oldChar, char newChar, int startIndex, int count ) {
  160. if( startIndex + count > sLength || startIndex < 0 || count < 0 ) {
  161. throw new ArgumentOutOfRangeException();
  162. }
  163. for( int replaceIterate = startIndex; replaceIterate < startIndex + count; replaceIterate++ ) {
  164. if( this[replaceIterate] == oldChar ) {
  165. this[replaceIterate] = newChar;
  166. }
  167. }
  168. return this;
  169. }
  170. public StringBuilder Replace( string oldValue, string newValue ) {
  171. return Replace( oldValue, newValue, 0, sLength );
  172. }
  173. public StringBuilder Replace( string oldValue, string newValue, int startIndex, int count ) {
  174. string startString = this.ToString();
  175. StringBuilder newStringB = new StringBuilder();
  176. string newString;
  177. if( oldValue == null ) {
  178. throw new ArgumentNullException(
  179. "The old value cannot be null.");
  180. }
  181. if( startIndex < 0 || count < 0 || startIndex + count > sLength ) {
  182. throw new ArgumentOutOfRangeException();
  183. }
  184. if( oldValue.Length == 0 ) {
  185. throw new ArgumentException(
  186. "The old value cannot be zero length.");
  187. }
  188. int nextIndex = startIndex; // Where to start the next search
  189. int lastIndex = nextIndex; // Where the last search finished
  190. while( nextIndex != -1 ) {
  191. nextIndex = startString.IndexOf( oldValue, lastIndex);
  192. if( nextIndex != -1 ) {
  193. // The MS implementation won't replace a substring
  194. // if that substring goes over the "count"
  195. // boundary, so we'll make sure the behaviour
  196. // here is the same.
  197. if( nextIndex + oldValue.Length <= startIndex + count ) {
  198. // Add everything to the left of the old
  199. // string
  200. newStringB.Append( startString.Substring( lastIndex, nextIndex - lastIndex ) );
  201. // Add the replacement string
  202. newStringB.Append( newValue );
  203. // Set the next start point to the
  204. // end of the last match
  205. lastIndex = nextIndex + oldValue.Length;
  206. } else {
  207. // We're past the "count" we're supposed to replace within
  208. nextIndex = -1;
  209. newStringB.Append(
  210. startString.Substring( lastIndex ) );
  211. }
  212. } else {
  213. // Append everything left over
  214. newStringB.Append( startString.Substring( lastIndex ) );
  215. }
  216. }
  217. newString = newStringB.ToString();
  218. EnsureCapacity( newString.Length );
  219. sString = newString.ToCharArray();
  220. sLength = newString.Length;
  221. return this;
  222. }
  223. /* The Append Methods */
  224. // TODO: Currently most of these methods convert the
  225. // parameter to a CharArray (via a String) and then pass
  226. // it to Append( char[] ). There might be a faster way
  227. // of doing this, but it's probably adequate and anything else
  228. // would make it too messy.
  229. //
  230. // As an example, a sample test run of appending a 100 character
  231. // string to the StringBuilder, and loooping this 50,000 times
  232. // results in an elapsed time of 2.4s using the MS StringBuilder
  233. // and 2.7s using this StringBuilder. Note that this results
  234. // in a 5 million character string. I believe MS uses a lot
  235. // of "native" DLLs for the "meat" of the base classes.
  236. public StringBuilder Append( char[] value ) {
  237. if( sLength + value.Length > sCapacity ) {
  238. // Need more capacity, double the capacity StringBuilder
  239. // and make sure we have at least enough for the value
  240. // if that's going to go over double.
  241. Capacity = value.Length + ( sCapacity + sCapacity);
  242. }
  243. Array.Copy( value, 0, sString, sLength, value.Length );
  244. sLength += value.Length;
  245. return this;
  246. }
  247. public StringBuilder Append( string value ) {
  248. if( value != null ) {
  249. return Append( value.ToCharArray() );
  250. } else {
  251. return null;
  252. }
  253. }
  254. public StringBuilder Append( bool value ) {
  255. return Append( value.ToString().ToCharArray() );
  256. }
  257. public StringBuilder Append( byte value ) {
  258. return Append( value.ToString().ToCharArray() );
  259. }
  260. public StringBuilder Append( int index, char value) {
  261. char[] appendChar = new char[1];
  262. appendChar[0] = value;
  263. return Append( appendChar );
  264. }
  265. public StringBuilder Append( decimal value ) {
  266. return Append( value.ToString().ToCharArray() );
  267. }
  268. public StringBuilder Append( double value ) {
  269. return Append( value.ToString().ToCharArray() );
  270. }
  271. public StringBuilder Append( short value ) {
  272. return Append( value.ToString().ToCharArray() );
  273. }
  274. public StringBuilder Append( int value ) {
  275. return Append( value.ToString().ToCharArray() );
  276. }
  277. public StringBuilder Append( long value ) {
  278. return Append( value.ToString().ToCharArray() );
  279. }
  280. public StringBuilder Append( object value ) {
  281. return Append( value.ToString().ToCharArray() );
  282. }
  283. public StringBuilder Append( sbyte value ) {
  284. return Append( value.ToString().ToCharArray() );
  285. }
  286. public StringBuilder Append( float value ) {
  287. return Append( value.ToString().ToCharArray() );
  288. }
  289. public StringBuilder Append( ushort value ) {
  290. return Append( value.ToString().ToCharArray() );
  291. }
  292. public StringBuilder Append( uint value ) {
  293. return Append( value.ToString().ToCharArray() );
  294. }
  295. public StringBuilder Append( ulong value ) {
  296. return Append( value.ToString().ToCharArray() );
  297. }
  298. public StringBuilder Append( char value ) {
  299. return Append (value, 1);
  300. }
  301. public StringBuilder Append( char value, int repeatCount ) {
  302. if( repeatCount < 0 ) {
  303. throw new ArgumentOutOfRangeException();
  304. }
  305. return Append( new String( value, repeatCount) );
  306. }
  307. public StringBuilder Append( char[] value, int startIndex, int charCount ) {
  308. if( (charCount < 0 || startIndex < 0) ||
  309. ( charCount + startIndex > value.Length ) ) {
  310. throw new ArgumentOutOfRangeException();
  311. }
  312. if( value == null ) {
  313. if( !(startIndex == 0 && charCount == 0) ) {
  314. throw new ArgumentNullException();
  315. } else {
  316. return this;
  317. }
  318. } else {
  319. char[] appendChars = new char[ charCount ];
  320. Array.Copy( value, startIndex, appendChars, 0, charCount );
  321. return Append( appendChars );
  322. }
  323. }
  324. public StringBuilder Append( string value, int startIndex, int count ) {
  325. if( (count < 0 || startIndex < 0) ||
  326. ( startIndex + count > value.Length ) ) {
  327. throw new ArgumentOutOfRangeException();
  328. }
  329. return Append( value.Substring( startIndex, count ).ToCharArray() );
  330. }
  331. public StringBuilder AppendFormat( string format, object arg0 ) {
  332. // TODO: Implement
  333. return this;
  334. }
  335. public StringBuilder AppendFormat( string format, params object[] args ) {
  336. // TODO: Implement
  337. return this;
  338. }
  339. public StringBuilder AppendFormat( IFormatProvider provider, string format,
  340. params object[] args ) {
  341. // TODO: Implement
  342. return this;
  343. }
  344. public StringBuilder AppendFormat( string format, object arg0, object arg1 ) {
  345. // TODO: Implement;
  346. return this;
  347. }
  348. public StringBuilder AppendFormat( string format, object arg0, object arg1, object arg2 ) {
  349. // TODO Implement
  350. return this;
  351. }
  352. /* The Insert Functions */
  353. // Similarly to the Append functions, get everything down to a CharArray
  354. // and insert that.
  355. public StringBuilder Insert( int index, char[] value ) {
  356. if( index > sLength || index < 0) {
  357. throw new ArgumentOutOfRangeException();
  358. }
  359. if( value == null || value.Length == 0 ) {
  360. return this;
  361. } else {
  362. // Check we have the capacity to insert this array
  363. if( sCapacity < sLength + value.Length ) {
  364. Capacity = value.Length + ( sCapacity + sCapacity );
  365. }
  366. // Move everything to the right of the insert point across
  367. Array.Copy( sString, index, sString, index + value.Length, sLength - index);
  368. // Copy in stuff from the insert buffer
  369. Array.Copy( value, 0, sString, index, value.Length );
  370. sLength += value.Length;
  371. return this;
  372. }
  373. }
  374. public StringBuilder Insert( int index, string value ) {
  375. return Insert( index, value.ToCharArray() );
  376. }
  377. public StringBuilder Insert( int index, bool value ) {
  378. return Insert( index, value.ToString().ToCharArray() );
  379. }
  380. public StringBuilder Insert( int index, byte value ) {
  381. return Insert( index, value.ToString().ToCharArray() );
  382. }
  383. public StringBuilder Insert( int index, char value) {
  384. char[] insertChar = new char[1];
  385. insertChar[0] = value;
  386. return Insert( index, insertChar );
  387. }
  388. public StringBuilder Insert( int index, decimal value ) {
  389. return Insert( index, value.ToString().ToCharArray() );
  390. }
  391. public StringBuilder Insert( int index, double value ) {
  392. return Insert( index, value.ToString().ToCharArray() );
  393. }
  394. public StringBuilder Insert( int index, short value ) {
  395. return Insert( index, value.ToString().ToCharArray() );
  396. }
  397. public StringBuilder Insert( int index, int value ) {
  398. return Insert( index, value.ToString().ToCharArray() );
  399. }
  400. public StringBuilder Insert( int index, long value ) {
  401. return Insert( index, value.ToString().ToCharArray() );
  402. }
  403. public StringBuilder Insert( int index, object value ) {
  404. return Insert( index, value.ToString().ToCharArray() );
  405. }
  406. public StringBuilder Insert( int index, sbyte value ) {
  407. return Insert( index, value.ToString().ToCharArray() );
  408. }
  409. public StringBuilder Insert( int index, float value ) {
  410. return Insert( index, value.ToString().ToCharArray() );
  411. }
  412. public StringBuilder Insert( int index, ushort value ) {
  413. return Insert( index, value.ToString().ToCharArray() );
  414. }
  415. public StringBuilder Insert( int index, uint value ) {
  416. return Insert( index, value.ToString().ToCharArray() );
  417. }
  418. public StringBuilder Insert( int index, ulong value ) {
  419. return Insert( index, value.ToString().ToCharArray() );
  420. }
  421. public StringBuilder Insert( int index, string value, int count ) {
  422. if ( count < 0 ) {
  423. throw new ArgumentOutOfRangeException();
  424. }
  425. if( value != null ) {
  426. if( value != "" ) {
  427. for( int insertCount = 0; insertCount < count;
  428. insertCount++ ) {
  429. Insert( index, value.ToCharArray() );
  430. }
  431. }
  432. }
  433. return this;
  434. }
  435. public StringBuilder Insert( int index, char[] value, int startIndex,
  436. int charCount ) {
  437. if( value != null ) {
  438. if( charCount < 0 || startIndex < 0 || startIndex + charCount > value.Length ) {
  439. throw new ArgumentOutOfRangeException();
  440. }
  441. char[] insertChars = new char[ charCount ];
  442. Array.Copy( value, startIndex, insertChars, 0, charCount );
  443. return Insert( index, insertChars );
  444. } else {
  445. return this;
  446. }
  447. }
  448. }
  449. }