1 // -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
6 // Jeffrey Stedfast (fejj@ximian.com)
7 // Dan Lewis (dihlewis@yahoo.co.uk)
9 // (C) 2001 Ximian, Inc. http://www.ximian.com
12 // FIXME: from what I gather from msdn, when a function is to return an empty string
13 // we should be returning this.Empty - some methods do this and others don't.
15 // FIXME: I didn't realise until later that `string' has a .Length method and so
16 // I am missing some proper bounds-checking in some methods. Find these
17 // instances and throw the ArgumentOutOfBoundsException at the programmer.
18 // I like pelting programmers with ArgumentOutOfBoundsException's :-)
20 // FIXME: The ToLower(), ToUpper(), and Compare(..., bool ignoreCase) methods
21 // need to be made unicode aware.
23 // FIXME: when you have a char carr[], does carr.Length include the terminating null char?
27 using System.Collections;
28 using System.Globalization;
29 using System.Runtime.CompilerServices;
33 //[DefaultMemberName("Chars")]
34 public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable {
35 public static readonly string Empty = "";
41 internal String (int storage)
44 throw new ArgumentOutOfRangeException ();
46 c_str = new char [storage];
50 unsafe public String (char *value)
54 // FIXME: can I do value.Length here?
58 for (i = 0; *(value + i) != '\0'; i++);
62 this.c_str = new char [this.length + 1];
63 for (i = 0; i < this.length; i++)
64 this.c_str[i] = *(value + i);
67 public String (char[] value)
71 // FIXME: value.Length includes the terminating null char?
72 this.length = value != null ? strlen (value): 0;
73 this.c_str = new char [this.length + 1];
74 for (i = 0; i < this.length; i++)
75 this.c_str[i] = value[i];
79 unsafe public String (sbyte *value)
81 // FIXME: consider unicode?
84 // FIXME: can I do value.Length here? */
88 for (i = 0; *(value + i) != '\0'; i++);
92 this.c_str = new char [this.length + 1];
93 for (i = 0; i < this.length; i++)
94 this.c_str[i] = (char) *(value + i);
97 public String (char c, int count)
102 this.c_str = new char [count + 1];
103 for (i = 0; i < count; i++)
107 [CLSCompliant(false)]
108 unsafe public String (char *value, int startIndex, int length)
112 if (value == null && startIndex != 0 && length != 0)
113 throw new ArgumentNullException ();
115 if (startIndex < 0 || length < 0)
116 throw new ArgumentOutOfRangeException ();
118 this.length = length;
119 this.c_str = new char [length + 1];
120 for (i = 0; i < length; i++)
121 this.c_str[i] = *(value + startIndex + i);
124 public String (char[] value, int startIndex, int length)
128 if (value == null && startIndex != 0 && length != 0)
129 throw new ArgumentNullException ();
131 if (startIndex < 0 || length < 0)
132 throw new ArgumentOutOfRangeException ();
134 this.length = length;
135 this.c_str = new char [length + 1];
136 for (i = 0; i < length; i++)
137 this.c_str[i] = value[startIndex + i];
140 [CLSCompliant(false)]
141 unsafe public String (sbyte *value, int startIndex, int length)
143 // FIXME: consider unicode?
146 if (value == null && startIndex != 0 && length != 0)
147 throw new ArgumentNullException ();
149 if (startIndex < 0 || length < 0)
150 throw new ArgumentOutOfRangeException ();
152 this.length = length;
153 this.c_str = new char [length + 1];
154 for (i = 0; i < length; i++)
155 this.c_str[i] = (char) *(value + startIndex + i);
158 [CLSCompliant(false)][MonoTODO]
159 unsafe public String (sbyte *value, int startIndex, int length, Encoding enc)
161 // FIXME: implement me
166 // FIXME: is there anything we need to do here?
167 /*base.Finalize ();*/
177 [IndexerName("Chars")]
178 public char this [int index] {
180 if (index >= this.length)
181 throw new ArgumentOutOfRangeException ();
183 return this.c_str[index];
187 // Private helper methods
188 private static int strlen (char[] str)
190 // FIXME: if str.Length includes terminating null char, then return (str.Length - 1)
195 private static char tolowerordinal (char c)
197 // FIXME: implement me
201 private static bool is_lwsp (char c)
203 /* this comes from the msdn docs for String.Trim() */
204 if ((c >= '\x9' && c <= '\xD') || c == '\x20' || c == '\xA0' ||
205 (c >= '\x2000' && c <= '\x200B') || c == '\x3000' || c == '\xFEFF')
211 private static int BoyerMoore (char[] haystack, string needle, int startIndex, int count)
213 /* (hopefully) Unicode-safe Boyer-Moore implementation */
214 int[] skiptable = new int[65536]; /* our unicode-safe skip-table */
215 int h, n, he, ne, hc, nc, i;
217 if (haystack == null || needle == null)
218 throw new ArgumentNullException ();
220 /* if the search buffer is shorter than the pattern buffer, we can't match */
221 if (count < needle.length)
224 /* return an instant match if the pattern is 0-length */
225 if (needle.length == 0)
228 /* set a pointer at the end of each string */
229 ne = needle.length - 1; /* position of char before '\0' */
230 he = startIndex + count; /* position of last valid char */
232 /* init the skip table with the pattern length */
234 for (i = 0; i < 65536; i++)
237 /* set the skip value for the chars that *do* appear in the
238 * pattern buffer (needle) to the distance from the index to
239 * the end of the pattern buffer. */
240 for (nc = 0; nc < ne; nc++)
241 skiptable[(int) needle[nc]] = ne - nc;
244 while (count >= needle.length) {
245 hc = h + needle.length - 1; /* set the haystack compare pointer */
246 nc = ne; /* set the needle compare pointer */
248 /* work our way backwards until they don't match */
249 for (i = 0; nc > 0; nc--, hc--, i++)
250 if (needle[nc] != haystack[hc])
253 if (needle[nc] != haystack[hc]) {
254 n = skiptable[(int) haystack[hc]] - i;
266 public object Clone ()
268 // FIXME: implement me
272 internal enum _StringCompareMode {
274 CompareCaseInsensitive,
278 internal static int _CompareGetLength (string strA, string strB)
280 if ((strA == null) || (strB == null))
283 return Math.Max (strA.Length, strB.Length);
286 internal static int _CompareChar (char chrA, char chrB, CultureInfo culture,
287 _StringCompareMode mode)
292 case _StringCompareMode.CompareDirect:
293 // FIXME: We should do a culture based comparision here,
294 // but for the moment let's do it by hand.
295 // In the microsoft runtime, uppercase letters
296 // sort after lowercase letters in the default
298 if (Char.IsUpper (chrA) && Char.IsLower (chrB))
300 else if (Char.IsLower (chrA) && Char.IsUpper (chrB))
302 result = (int) (chrA - chrB);
304 case _StringCompareMode.CompareCaseInsensitive:
305 result = (int) (Char.ToLower (chrA) - Char.ToLower (chrB));
307 case _StringCompareMode.CompareOrdinal:
308 result = (int) (tolowerordinal (chrA) - tolowerordinal (chrB));
320 internal static int _Compare (string strA, int indexA, string strB, int indexB,
321 int length, CultureInfo culture,
322 _StringCompareMode mode)
327 /* When will the hurting stop!?!? */
333 } else if (strB == null)
336 if (length < 0 || indexA < 0 || indexB < 0)
337 throw new ArgumentOutOfRangeException ();
339 if (indexA > strA.Length || indexB > strB.Length)
340 throw new ArgumentOutOfRangeException ();
342 // FIXME: Implement culture
344 throw new NotImplementedException ();
346 for (i = 0; i < length - 1; i++) {
347 if ((indexA+i >= strA.Length) || (indexB+i >= strB.Length))
350 if (_CompareChar (strA[indexA+i], strB[indexB+i], culture, mode) != 0)
354 if (indexA+i >= strA.Length) {
355 if (indexB+i >= strB.Length)
359 } else if (indexB+i >= strB.Length)
362 return _CompareChar (strA[indexA+i], strB[indexB+i], culture, mode);
366 public static int Compare (string strA, string strB)
368 return Compare (strA, strB, false);
371 public static int Compare (string strA, string strB, bool ignoreCase)
373 return Compare (strA, strB, ignoreCase, null);
376 public static int Compare (string strA, string strB, bool ignoreCase, CultureInfo culture)
378 return Compare (strA, 0, strB, 0,
379 _CompareGetLength (strA, strB),
380 ignoreCase, culture);
383 public static int Compare (string strA, int indexA, string strB, int indexB, int length)
385 return Compare (strA, indexA, strB, indexB, length, false);
388 public static int Compare (string strA, int indexA, string strB, int indexB,
389 int length, bool ignoreCase)
391 return Compare (strA, indexA, strB, indexB, length, ignoreCase, null);
394 public static int Compare (string strA, int indexA, string strB, int indexB,
395 int length, bool ignoreCase, CultureInfo culture)
397 _StringCompareMode mode;
399 mode = ignoreCase ? _StringCompareMode.CompareCaseInsensitive :
400 _StringCompareMode.CompareDirect;
402 return _Compare (strA, indexA, strB, indexB, length, culture, mode);
405 public static int CompareOrdinal (string strA, string strB)
407 return CompareOrdinal (strA, 0, strB, 0, _CompareGetLength (strA, strB));
410 public static int CompareOrdinal (string strA, int indexA, string strB, int indexB,
413 return _Compare (strA, indexA, strB, indexB, length, null,
414 _StringCompareMode.CompareOrdinal);
417 public int CompareTo (object obj)
419 return Compare (this, obj == null ? null : obj.ToString ());
422 public int CompareTo (string str)
424 return Compare (this, str);
427 public static string Concat (object arg)
429 return arg != null ? arg.ToString () : String.Empty;
432 public static string Concat (params object[] args)
439 throw new ArgumentNullException ();
441 strings = new string [args.Length];
444 foreach (object arg in args) {
445 /* use Empty for each null argument */
447 strings[i] = String.Empty;
449 strings[i] = arg.ToString ();
450 len += strings[i].length;
457 String res = new String (len);
460 for (int j = 0; j < strings.Length; j++)
461 for (int k = 0; k < strings[j].length; k++)
462 str[i++] = strings[j].c_str[k];
467 public static string Concat (params string[] values)
473 throw new ArgumentNullException ();
476 foreach (string value in values)
477 len += value != null ? value.Length : 0;
482 String res = new String (len);
485 foreach (string value in values) {
489 for (int j = 0; j < value.length; j++)
490 str[i++] = value.c_str[j];
496 public static string Concat (object arg0, object arg1)
498 string str0 = arg0 != null ? arg0.ToString () : String.Empty;
499 string str1 = arg1 != null ? arg1.ToString () : String.Empty;
501 return Concat (str0, str1);
504 public static string Concat (string str0, string str1)
514 len = str0.length + str1.length;
518 String res = new String (len);
521 for (i = 0; i < str0.length; i++)
522 concat[i] = str0.c_str[i];
523 for (j = 0 ; j < str1.length; j++)
524 concat[i + j] = str1.c_str[j];
529 public static string Concat (object arg0, object arg1, object arg2)
531 string str0 = arg0 != null ? arg0.ToString () : String.Empty;
532 string str1 = arg1 != null ? arg1.ToString () : String.Empty;
533 string str2 = arg2 != null ? arg2.ToString () : String.Empty;
535 return Concat (str0, str1, str2);
538 public static string Concat (string str0, string str1, string str2)
550 len = str0.length + str1.length + str2.length;
554 String res = new String (len);
557 for (i = 0; i < str0.length; i++)
558 concat[i] = str0.c_str[i];
559 for (j = 0; j < str1.length; j++)
560 concat[i + j] = str1.c_str[j];
561 for (k = 0; k < str2.length; k++)
562 concat[i + j + k] = str2.c_str[k];
567 public static string Concat (string str0, string str1, string str2, string str3)
581 len = str0.length + str1.length + str2.length + str3.length;
584 String res = new String (len);
587 for (i = 0; i < str0.length; i++)
588 concat[i] = str0.c_str[i];
589 for (j = 0; j < str1.length; j++)
590 concat[i + j] = str1.c_str[j];
591 for (k = 0; k < str2.length; k++)
592 concat[i + j + k] = str2.c_str[k];
593 for (l = 0; l < str3.length; l++)
594 concat[i + j + k + l] = str3.c_str[l];
599 public static string Copy (string str)
601 // FIXME: how do I *copy* a string if I can only have 1 of each?
603 throw new ArgumentNullException ();
608 public void CopyTo (int sourceIndex, char[] destination, int destinationIndex, int count)
610 // LAMESPEC: should I null-terminate?
613 if (destination == null)
614 throw new ArgumentNullException ();
616 if (sourceIndex < 0 || destinationIndex < 0 || count < 0)
617 throw new ArgumentOutOfRangeException ();
619 if (sourceIndex + count > this.length)
620 throw new ArgumentOutOfRangeException ();
622 if (destinationIndex + count > destination.Length)
623 throw new ArgumentOutOfRangeException ();
625 for (i = 0; i < count; i++)
626 destination[destinationIndex + i] = this.c_str[sourceIndex + i];
629 public bool EndsWith (string value)
631 bool endswith = true;
635 throw new ArgumentNullException ();
637 start = this.length - value.length;
641 for (i = start; i < this.length && endswith; i++)
642 endswith = this.c_str[i] == value.c_str[i - start];
647 public override bool Equals (object obj)
649 if (!(obj is String))
652 return this == (String) obj;
655 public bool Equals (string value)
657 return this == value;
660 public static bool Equals (string a, string b)
665 public static string Format (string format, object arg0) {
666 return Format (null, format, new object[] { arg0 });
669 public static string Format (string format, object arg0, object arg1) {
670 return Format (null, format, new object[] { arg0, arg1 });
673 public static string Format (string format, object arg0, object arg1, object arg2) {
674 return Format (null, format, new object[] { arg0, arg1, arg2 });
677 public static string Format (string format, params object[] args) {
678 return Format (null, format, args);
681 public static string Format (IFormatProvider provider, string format, params object[] args) {
682 if (format == null || args == null)
683 throw new ArgumentNullException ();
685 StringBuilder result = new StringBuilder ();
689 while (ptr < format.Length) {
690 char c = format[ptr ++];
693 result.Append (format, start, ptr - start - 1);
695 // check for escaped open bracket
697 if (format[ptr] == '{') {
708 ParseFormatSpecifier (format, ref ptr, out n, out width, out left_align, out arg_format);
709 if (n >= args.Length)
710 throw new FormatException ("Index (zero based) must be greater than or equal to zero and less than the size of the argument list.");
714 object arg = args[n];
719 else if (arg is IFormattable)
720 str = ((IFormattable)arg).ToString (arg_format, provider);
722 str = arg.ToString ();
724 // pad formatted string and append to result
726 if (width > str.Length) {
727 string pad = new String (' ', width - str.Length);
743 else if (c == '}' && format[ptr] == '}') {
744 result.Append (format, start, ptr - start - 1);
749 if (start < format.Length)
750 result.Append (format.Substring (start));
752 return result.ToString ();
755 public CharEnumerator GetEnumerator ()
757 return new CharEnumerator (this);
760 IEnumerator IEnumerable.GetEnumerator ()
762 return new CharEnumerator (this);
765 public override int GetHashCode ()
769 for (i = 0; i < length; ++i)
770 h = (h << 5) - h + c_str [i];
774 public TypeCode GetTypeCode ()
776 return TypeCode.String;
779 public int IndexOf (char value)
781 return IndexOf (value, 0, this.length);
784 public int IndexOf (string value)
786 return IndexOf (value, 0, this.length);
789 public int IndexOf (char value, int startIndex)
791 return IndexOf (value, startIndex, this.length - startIndex);
794 public int IndexOf (string value, int startIndex)
796 return IndexOf (value, startIndex, this.length - startIndex);
799 public int IndexOf (char value, int startIndex, int count)
803 if (startIndex < 0 || count < 0 || startIndex + count > this.length)
804 throw new ArgumentOutOfRangeException ();
806 for (i = startIndex; i - startIndex < count; i++)
807 if (this.c_str[i] == value)
813 public int IndexOf (string value, int startIndex, int count)
816 throw new ArgumentNullException ();
818 if (startIndex < 0 || count < 0 || startIndex + count > this.length)
819 throw new ArgumentOutOfRangeException ();
821 return BoyerMoore (this.c_str, value, startIndex, count);
824 for (i = startIndex; i - startIndex + value.Length <= count; ) {
825 if (this.c_str[i] == value[0]) {
829 for (j = 1; equal && j < value.Length; j++) {
830 equal = this.c_str[i + j] == value[j];
831 if (this.c_str[i + j] == value[0] && nexti == 0)
850 public int IndexOfAny (char[] values)
852 return IndexOfAny (values, 0, this.length);
855 public int IndexOfAny (char[] values, int startIndex)
857 return IndexOfAny (values, startIndex, this.length - startIndex);
860 public int IndexOfAny (char[] values, int startIndex, int count)
863 throw new ArgumentNullException ();
865 if (startIndex < 0 || count < 0 || startIndex + count > this.length)
866 throw new ArgumentOutOfRangeException ();
868 for (int i = startIndex; i < startIndex + count; i++) {
869 for (int j = 0; j < strlen (values); j++) {
870 if (this.c_str[i] == values[j])
878 public string Insert (int startIndex, string value)
884 throw new ArgumentNullException ();
886 if (startIndex < 0 || startIndex > this.length)
887 throw new ArgumentOutOfRangeException ();
889 String res = new String (value.length + this.length);
892 for (i = 0; i < startIndex; i++)
893 str[i] = this.c_str[i];
894 for (j = 0; j < value.length; j++)
895 str[i + j] = value.c_str[j];
896 for ( ; i < this.length; i++)
897 str[i + j] = this.c_str[i];
902 [MethodImplAttribute(MethodImplOptions.InternalCall)]
903 internal extern static string _Intern (string str);
905 public static string Intern (string str)
908 throw new ArgumentNullException ();
910 return _Intern (str);
913 [MethodImplAttribute(MethodImplOptions.InternalCall)]
914 internal extern static string _IsInterned (string str);
916 public static string IsInterned (string str)
919 throw new ArgumentNullException ();
921 return _IsInterned (str);
924 public static string Join (string separator, string[] value)
926 return Join (separator, value, 0, value.Length);
929 public static string Join (string separator, string[] value, int startIndex, int count)
931 // LAMESPEC: msdn doesn't specify what happens when separator is null
935 if (separator == null || value == null)
936 throw new ArgumentNullException ();
938 if (startIndex + count > value.Length)
939 throw new ArgumentOutOfRangeException ();
942 for (i = startIndex, used = 0; used < count; i++, used++) {
944 len += separator.length;
946 len += value[i].length;
949 // We have no elements to join?
953 String res = new String (len);
956 for (i = 0; i < value[startIndex].length; i++)
957 str[i] = value[startIndex][i];
960 for (j = startIndex + 1; used < count; j++, used++) {
963 for (k = 0; k < separator.length; k++)
964 str[i++] = separator.c_str[k];
965 for (k = 0; k < value[j].length; k++)
966 str[i++] = value[j].c_str[k];
972 public int LastIndexOf (char value)
978 for (; i >= 0; i--) {
979 if (this.c_str[i] == value)
986 public int LastIndexOf (string value)
988 return LastIndexOf (value, this.length - 1, this.length);
991 public int LastIndexOf (char value, int startIndex)
993 if (startIndex < 0 || startIndex >= this.length)
994 throw new ArgumentOutOfRangeException ();
996 for (int i = startIndex; i >= 0; i--) {
997 if (this.c_str[i] == value)
1004 public int LastIndexOf (string value, int startIndex)
1006 return LastIndexOf (value, startIndex, startIndex + 1);
1009 public int LastIndexOf (char value, int startIndex, int count)
1011 if (startIndex < 0 || count < 0)
1012 throw new ArgumentOutOfRangeException ();
1014 if (startIndex >= this.length || startIndex - count + 1 < 0)
1015 throw new ArgumentOutOfRangeException ();
1017 for (int i = startIndex; i > startIndex - count; i--) {
1018 if (this.c_str[i] == value)
1025 public int LastIndexOf (string value, int startIndex, int count)
1027 // LAMESPEC: currently I'm using startIndex as the 0-position in the comparison,
1028 // but maybe it's the end-position in MS's implementation?
1029 // msdn is unclear on this point. I think this is correct though.
1033 throw new ArgumentNullException ();
1035 if (startIndex < 0 || startIndex >= this.length)
1036 throw new ArgumentOutOfRangeException ();
1038 if (count < 0 || startIndex - count + 1 < 0)
1039 throw new ArgumentOutOfRangeException ();
1041 if (value == String.Empty)
1044 if (startIndex + value.length > this.length) {
1045 /* just a little optimization */
1048 start = this.length - value.length;
1049 count -= startIndex - start;
1053 // FIXME: use a reversed-unicode-safe-Boyer-Moore?
1054 len = value.length - 1;
1055 for (i = startIndex; i > startIndex - count; i--) {
1056 if (this.c_str[i + len] == value.c_str[len]) {
1060 for (j = len - 1; equal && j >= 0; j--)
1061 equal = this.c_str[i + j] == value.c_str[j];
1071 public int LastIndexOfAny (char[] values)
1073 return LastIndexOfAny (values, this.length - 1, this.length);
1076 public int LastIndexOfAny (char[] values, int startIndex)
1078 return LastIndexOfAny (values, startIndex, startIndex + 1);
1081 public int LastIndexOfAny (char[] values, int startIndex, int count)
1086 throw new ArgumentNullException ();
1088 if (startIndex < 0 || count < 0 || startIndex - count + 1 < 0)
1089 throw new ArgumentOutOfRangeException ();
1091 for (i = startIndex; i > startIndex - count; i--) {
1092 for (int j = 0; j < strlen (values); j++) {
1093 if (this.c_str[i] == values[j])
1101 public string PadLeft (int totalWidth)
1103 return PadLeft (totalWidth, ' ');
1106 public string PadLeft (int totalWidth, char padChar)
1112 throw new ArgumentException ();
1114 str = new char [totalWidth > this.length ? totalWidth : this.length];
1115 for (i = 0; i < totalWidth - this.length; i++)
1118 for (j = 0; j < this.length; i++, j++)
1119 str[i] = this.c_str[j];
1121 return new String (str);
1124 public string PadRight (int totalWidth)
1126 return PadRight (totalWidth, ' ');
1129 public string PadRight (int totalWidth, char padChar)
1135 throw new ArgumentException ();
1137 str = new char [totalWidth > this.length ? totalWidth : this.length];
1138 for (i = 0; i < this.length; i++)
1139 str[i] = this.c_str[i];
1141 for ( ; i < str.Length; i++)
1144 return new String (str);
1147 public string Remove (int startIndex, int count)
1152 if (startIndex < 0 || count < 0 || startIndex + count > this.length)
1153 throw new ArgumentOutOfRangeException ();
1155 len = this.length - count;
1157 return String.Empty;
1159 String res = new String (len);
1161 for (i = 0; i < startIndex; i++)
1162 str[i] = this.c_str[i];
1163 for (j = i + count; j < this.length; j++)
1164 str[i++] = this.c_str[j];
1169 public string Replace (char oldChar, char newChar)
1174 String res = new String (length);
1176 for (i = 0; i < this.length; i++) {
1177 if (this.c_str[i] == oldChar)
1180 str[i] = this.c_str[i];
1186 public string Replace (string oldValue, string newValue)
1188 // LAMESPEC: msdn doesn't specify what to do if either args is null
1189 int index, len, i, j;
1192 if (oldValue == null || newValue == null)
1193 throw new ArgumentNullException ();
1195 // Use IndexOf in case I later rewrite it to use Boyer-Moore
1196 index = IndexOf (oldValue, 0);
1198 // This is the easy one ;-)
1199 return Substring (0, this.length);
1202 len = this.length - oldValue.length + newValue.length;
1204 return String.Empty;
1206 String res = new String (len);
1208 for (i = 0; i < index; i++)
1209 str[i] = this.c_str[i];
1210 for (j = 0; j < newValue.length; j++)
1211 str[i++] = newValue[j];
1212 for (j = index + oldValue.length; j < this.length; j++)
1213 str[i++] = this.c_str[j];
1218 private int splitme (char[] separators, int startIndex)
1220 /* this is basically a customized IndexOfAny() for the Split() methods */
1221 for (int i = startIndex; i < this.length; i++) {
1222 if (separators != null) {
1223 foreach (char sep in separators) {
1224 if (this.c_str[i] == sep)
1225 return i - startIndex;
1227 } else if (is_lwsp (this.c_str[i])) {
1228 return i - startIndex;
1235 public string[] Split (params char[] separator)
1239 * @separator: delimiting chars or null to split on whtspc
1241 * Returns: 1. An array consisting of a single
1242 * element (@this) if none of the delimiting
1243 * chars appear in @this. 2. An array of
1244 * substrings which are delimited by one of
1245 * the separator chars. 3. An array of
1246 * substrings separated by whitespace if
1247 * @separator is null. The Empty string should
1248 * be returned wherever 2 delimiting chars are
1251 // FIXME: would using a Queue be better?
1256 list = new ArrayList ();
1257 for (index = 0, len = 0; index < this.length; index += len + 1) {
1258 len = splitme (separator, index);
1259 len = len > -1 ? len : this.length - index;
1261 list.Add (String.Empty);
1266 str = new char [len];
1267 for (i = 0; i < len; i++)
1268 str[i] = this.c_str[index + i];
1270 list.Add (new String (str));
1274 strings = new string [list.Count];
1275 if (list.Count == 1) {
1276 /* special case for an array holding @this */
1279 for (index = 0; index < list.Count; index++)
1280 strings[index] = (string) list[index];
1286 public string[] Split (char[] separator, int maxCount)
1288 // FIXME: what to do if maxCount <= 0?
1289 // FIXME: would using Queue be better than ArrayList?
1292 int index, len, used;
1295 list = new ArrayList ();
1296 for (index = 0, len = 0; index < this.length && used < maxCount; index += len + 1) {
1297 len = splitme (separator, index);
1298 len = len > -1 ? len : this.length - index;
1300 list.Add (String.Empty);
1305 str = new char [len];
1306 for (i = 0; i < len; i++)
1307 str[i] = this.c_str[index + i];
1309 list.Add (new String (str));
1314 /* fit the remaining chunk of the @this into it's own element */
1315 if (index < this.length - 1) {
1319 str = new char [this.length - index];
1320 for (i = index; i < this.length; i++)
1321 str[i - index] = this.c_str[i];
1323 list.Add (new String (str));
1326 strings = new string [list.Count];
1327 if (list.Count == 1) {
1328 /* special case for an array holding @this */
1331 for (index = 0; index < list.Count; index++)
1332 strings[index] = (string) list[index];
1338 public bool StartsWith (string value)
1340 bool startswith = true;
1344 throw new ArgumentNullException ();
1346 if (value.length > this.length)
1349 for (i = 0; i < value.length && startswith; i++)
1350 startswith = startswith && value.c_str[i] == this.c_str[i];
1355 public string Substring (int startIndex)
1360 if (startIndex < 0 || startIndex > this.length)
1361 throw new ArgumentOutOfRangeException ();
1363 len = this.length - startIndex;
1365 return String.Empty;
1366 String res = new String (len);
1368 for (i = startIndex; i < this.length; i++)
1369 str[i - startIndex] = this.c_str[i];
1374 public string Substring (int startIndex, int length)
1379 if (startIndex < 0 || length < 0 || startIndex + length > this.length)
1380 throw new ArgumentOutOfRangeException ();
1383 return String.Empty;
1385 String res = new String (length);
1387 for (i = startIndex; i < startIndex + length; i++)
1388 str[i - startIndex] = this.c_str[i];
1393 bool IConvertible.ToBoolean (IFormatProvider provider)
1395 return Convert.ToBoolean (this);
1398 byte IConvertible.ToByte (IFormatProvider provider)
1400 return Convert.ToByte (this);
1403 char IConvertible.ToChar (IFormatProvider provider)
1405 return Convert.ToChar (this);
1408 public char[] ToCharArray ()
1410 return ToCharArray (0, this.length);
1413 public char[] ToCharArray (int startIndex, int length)
1418 if (startIndex < 0 || length < 0 || startIndex + length > this.length)
1419 throw new ArgumentOutOfRangeException ();
1421 chars = new char [length];
1422 for (i = startIndex; i < length; i++)
1423 chars[i - startIndex] = this.c_str[i];
1428 DateTime IConvertible.ToDateTime (IFormatProvider provider)
1430 return Convert.ToDateTime (this);
1433 decimal IConvertible.ToDecimal (IFormatProvider provider)
1435 return Convert.ToDecimal (this);
1438 double IConvertible.ToDouble (IFormatProvider provider)
1440 return Convert.ToDouble (this);
1443 short IConvertible.ToInt16 (IFormatProvider provider)
1445 return Convert.ToInt16 (this);
1448 int IConvertible.ToInt32 (IFormatProvider provider)
1450 return Convert.ToInt32 (this);
1453 long IConvertible.ToInt64 (IFormatProvider provider)
1455 return Convert.ToInt64 (this);
1458 public string ToLower ()
1463 String res = new String (length);
1465 for (i = 0; i < this.length; i++)
1466 str[i] = Char.ToLower (this.c_str[i]);
1472 public string ToLower (CultureInfo culture)
1474 // FIXME: implement me
1475 throw new NotImplementedException ();
1479 [CLSCompliant(false)]
1480 sbyte IConvertible.ToSByte (IFormatProvider provider)
1482 return Convert.ToSByte (this);
1485 float IConvertible.ToSingle (IFormatProvider provider)
1487 return Convert.ToSingle (this);
1490 public override string ToString ()
1495 string IConvertible.ToString (IFormatProvider format)
1500 object IConvertible.ToType (Type conversionType, IFormatProvider provider)
1502 return Convert.ToType (this, conversionType, provider);
1505 [CLSCompliant(false)]
1506 ushort IConvertible.ToUInt16 (IFormatProvider provider)
1508 return Convert.ToUInt16 (this);
1511 [CLSCompliant(false)]
1512 uint IConvertible.ToUInt32 (IFormatProvider provider)
1514 return Convert.ToUInt32 (this);
1517 [CLSCompliant(false)]
1518 ulong IConvertible.ToUInt64 (IFormatProvider provider)
1520 return Convert.ToUInt64 (this);
1523 public string ToUpper ()
1528 String res = new String (length);
1530 for (i = 0; i < this.length; i++)
1531 str[i] = Char.ToUpper (this.c_str[i]);
1537 public string ToUpper (CultureInfo culture)
1539 // FIXME: implement me
1540 throw new NotImplementedException ();
1543 public string Trim ()
1548 public string Trim (params char[] trimChars)
1551 bool matches = false;
1553 for (begin = 0; begin < this.length; begin++) {
1554 if (trimChars != null) {
1556 foreach (char c in trimChars) {
1557 matches = this.c_str[begin] == c;
1564 matches = is_lwsp (this.c_str[begin]);
1571 for (end = this.length - 1; end > begin; end--) {
1572 if (trimChars != null) {
1574 foreach (char c in trimChars) {
1575 matches = this.c_str[end] == c;
1582 matches = is_lwsp (this.c_str[end]);
1591 return String.Empty;
1593 return Substring (begin, end - begin);
1596 public string TrimEnd (params char[] trimChars)
1598 bool matches = true;
1601 for (end = this.length - 1; matches && end > 0; end--) {
1603 if (trimChars != null) {
1605 foreach (char c in trimChars) {
1606 matches = this.c_str[end] == c;
1611 matches = is_lwsp (this.c_str[end]);
1615 return Substring (0, end+1);
1619 return String.Empty;
1621 return Substring (0, end);
1624 public string TrimStart (params char[] trimChars)
1626 bool matches = true;
1629 for (begin = 0; matches && begin < this.length; begin++) {
1630 if (trimChars != null) {
1632 foreach (char c in trimChars) {
1633 matches = this.c_str[begin] == c;
1638 matches = is_lwsp (this.c_str[begin]);
1642 return Substring (begin, this.length - begin);
1645 return String.Empty;
1649 public static bool operator ==(string a, string b)
1651 if ((object)a == null) {
1652 if ((object)b == null)
1656 if ((object)b == null)
1659 if (a.length != b.length)
1663 for (int i = 0; i < l; i++)
1664 if (a.c_str[i] != b.c_str[i])
1670 public static bool operator !=(string a, string b)
1677 private static void ParseFormatSpecifier (string str, ref int ptr, out int n, out int width, out bool left_align, out string format) {
1678 // parses format specifier of form:
1684 // N = argument number (non-negative integer)
1686 n = ParseDecimal (str, ref ptr);
1688 throw new FormatException ("Input string was not in correct format.");
1690 // M = width (non-negative integer)
1692 if (str[ptr] == ',') {
1693 left_align = (str[++ ptr] == '-');
1697 width = ParseDecimal (str, ref ptr);
1699 throw new FormatException ("Input string was not in correct format.");
1706 // F = argument format (string)
1708 if (str[ptr] == ':') {
1710 while (str[ptr] != '}')
1713 format = str.Substring (start, ptr - start);
1718 if (str[ptr ++] != '}')
1719 throw new FormatException ("Input string was not in correct format.");
1721 catch (IndexOutOfRangeException) {
1722 throw new FormatException ("Input string was not in correct format.");
1726 private static int ParseDecimal (string str, ref int ptr) {
1731 if (c < '0' || '9' < c)
1734 n = n * 10 + c - '0';