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")]
35 public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable {
36 public static readonly string Empty = "";
42 internal String (int storage)
45 throw new ArgumentOutOfRangeException ();
47 c_str = new char [storage];
51 unsafe public String (char *value)
55 // FIXME: can I do value.Length here?
59 for (i = 0; *(value + i) != '\0'; i++);
63 this.c_str = new char [this.length + 1];
64 for (i = 0; i < this.length; i++)
65 this.c_str[i] = *(value + i);
68 public String (char[] value)
72 // FIXME: value.Length includes the terminating null char?
73 this.length = value != null ? strlen (value): 0;
74 this.c_str = new char [this.length + 1];
75 for (i = 0; i < this.length; i++)
76 this.c_str[i] = value[i];
80 unsafe public String (sbyte *value)
82 // FIXME: consider unicode?
85 // FIXME: can I do value.Length here? */
89 for (i = 0; *(value + i) != '\0'; i++);
93 this.c_str = new char [this.length + 1];
94 for (i = 0; i < this.length; i++)
95 this.c_str[i] = (char) *(value + i);
98 public String (char c, int count)
103 this.c_str = new char [count + 1];
104 for (i = 0; i < count; i++)
108 [CLSCompliant(false)]
109 unsafe public String (char *value, int startIndex, int length)
113 if (value == null && startIndex != 0 && length != 0)
114 throw new ArgumentNullException ();
116 if (startIndex < 0 || length < 0)
117 throw new ArgumentOutOfRangeException ();
119 this.length = length;
120 this.c_str = new char [length + 1];
121 for (i = 0; i < length; i++)
122 this.c_str[i] = *(value + startIndex + i);
125 public String (char[] value, int startIndex, int length)
129 if (value == null && startIndex != 0 && length != 0)
130 throw new ArgumentNullException ();
132 if (startIndex < 0 || length < 0)
133 throw new ArgumentOutOfRangeException ();
135 this.length = length;
136 this.c_str = new char [length + 1];
137 for (i = 0; i < length; i++)
138 this.c_str[i] = value[startIndex + i];
141 [CLSCompliant(false)]
142 unsafe public String (sbyte *value, int startIndex, int length)
144 // FIXME: consider unicode?
147 if (value == null && startIndex != 0 && length != 0)
148 throw new ArgumentNullException ();
150 if (startIndex < 0 || length < 0)
151 throw new ArgumentOutOfRangeException ();
153 this.length = length;
154 this.c_str = new char [length + 1];
155 for (i = 0; i < length; i++)
156 this.c_str[i] = (char) *(value + startIndex + i);
159 [CLSCompliant(false)][MonoTODO]
160 unsafe public String (sbyte *value, int startIndex, int length, Encoding enc)
162 // FIXME: implement me
165 // FIXME: is there anything we need to do here?
180 [IndexerName("Chars")]
181 public char this [int index] {
183 if (index >= this.length)
184 throw new ArgumentOutOfRangeException ();
186 return this.c_str[index];
190 // Private helper methods
191 private static int strlen (char[] str)
193 // FIXME: if str.Length includes terminating null char, then return (str.Length - 1)
198 private static char tolowerordinal (char c)
200 // FIXME: implement me
204 private static bool is_lwsp (char c)
206 /* this comes from the msdn docs for String.Trim() */
207 if ((c >= '\x9' && c <= '\xD') || c == '\x20' || c == '\xA0' ||
208 (c >= '\x2000' && c <= '\x200B') || c == '\x3000' || c == '\xFEFF')
214 private static int BoyerMoore (char[] haystack, string needle, int startIndex, int count)
216 /* (hopefully) Unicode-safe Boyer-Moore implementation */
217 int[] skiptable = new int[65536]; /* our unicode-safe skip-table */
218 int h, n, he, ne, hc, nc, i;
220 if (haystack == null || needle == null)
221 throw new ArgumentNullException ();
223 /* if the search buffer is shorter than the pattern buffer, we can't match */
224 if (count < needle.length)
227 /* return an instant match if the pattern is 0-length */
228 if (needle.length == 0)
231 /* set a pointer at the end of each string */
232 ne = needle.length - 1; /* position of char before '\0' */
233 he = startIndex + count; /* position of last valid char */
235 /* init the skip table with the pattern length */
237 for (i = 0; i < 65536; i++)
240 /* set the skip value for the chars that *do* appear in the
241 * pattern buffer (needle) to the distance from the index to
242 * the end of the pattern buffer. */
243 for (nc = 0; nc < ne; nc++)
244 skiptable[(int) needle[nc]] = ne - nc;
247 while (count >= needle.length) {
248 hc = h + needle.length - 1; /* set the haystack compare pointer */
249 nc = ne; /* set the needle compare pointer */
251 /* work our way backwards until they don't match */
252 for (i = 0; nc > 0; nc--, hc--, i++)
253 if (needle[nc] != haystack[hc])
256 if (needle[nc] != haystack[hc]) {
257 n = skiptable[(int) haystack[hc]] - i;
269 public object Clone ()
271 // FIXME: implement me
275 const int StringCompareModeDirect = 0;
276 const int StringCompareModeCaseInsensitive = 1;
277 const int StringCompareModeOrdinal = 2;
279 internal static int _CompareGetLength (string strA, string strB)
281 if ((strA == null) || (strB == null))
284 return Math.Max (strA.Length, strB.Length);
287 internal static int _CompareChar (char chrA, char chrB, CultureInfo culture,
293 case StringCompareModeDirect:
294 // FIXME: We should do a culture based comparision here,
295 // but for the moment let's do it by hand.
296 // In the microsoft runtime, uppercase letters
297 // sort after lowercase letters in the default
299 if (Char.IsUpper (chrA) && Char.IsLower (chrB))
301 else if (Char.IsLower (chrA) && Char.IsUpper (chrB))
303 result = (int) (chrA - chrB);
305 case StringCompareModeCaseInsensitive:
306 result = (int) (Char.ToLower (chrA) - Char.ToLower (chrB));
308 case StringCompareModeOrdinal:
309 result = (int) (tolowerordinal (chrA) - tolowerordinal (chrB));
322 internal static int _Compare (string strA, int indexA, string strB, int indexB,
323 int length, CultureInfo culture,
329 /* When will the hurting stop!?!? */
335 } else if (strB == null)
338 if (length < 0 || indexA < 0 || indexB < 0)
339 throw new ArgumentOutOfRangeException ();
341 if (indexA > strA.Length || indexB > strB.Length)
342 throw new ArgumentOutOfRangeException ();
344 // FIXME: Implement culture
346 throw new NotImplementedException ();
348 for (i = 0; i < length - 1; i++) {
349 if ((indexA+i >= strA.Length) || (indexB+i >= strB.Length))
352 if (_CompareChar (strA[indexA+i], strB[indexB+i], culture, mode) != 0)
356 if (indexA+i >= strA.Length) {
357 if (indexB+i >= strB.Length)
361 } else if (indexB+i >= strB.Length)
364 return _CompareChar (strA[indexA+i], strB[indexB+i], culture, mode);
368 public static int Compare (string strA, string strB)
370 return Compare (strA, strB, false);
373 public static int Compare (string strA, string strB, bool ignoreCase)
375 return Compare (strA, strB, ignoreCase, null);
378 public static int Compare (string strA, string strB, bool ignoreCase, CultureInfo culture)
380 return Compare (strA, 0, strB, 0,
381 _CompareGetLength (strA, strB),
382 ignoreCase, culture);
385 public static int Compare (string strA, int indexA, string strB, int indexB, int length)
387 return Compare (strA, indexA, strB, indexB, length, false);
390 public static int Compare (string strA, int indexA, string strB, int indexB,
391 int length, bool ignoreCase)
393 return Compare (strA, indexA, strB, indexB, length, ignoreCase, null);
396 public static int Compare (string strA, int indexA, string strB, int indexB,
397 int length, bool ignoreCase, CultureInfo culture)
401 mode = ignoreCase ? StringCompareModeCaseInsensitive :
402 StringCompareModeDirect;
404 return _Compare (strA, indexA, strB, indexB, length, culture, mode);
407 public static int CompareOrdinal (string strA, string strB)
409 return CompareOrdinal (strA, 0, strB, 0, _CompareGetLength (strA, strB));
412 public static int CompareOrdinal (string strA, int indexA, string strB, int indexB,
415 return _Compare (strA, indexA, strB, indexB, length, null,
416 StringCompareModeOrdinal);
419 public int CompareTo (object obj)
421 return Compare (this, obj == null ? null : obj.ToString ());
424 public int CompareTo (string str)
426 return Compare (this, str);
429 public static string Concat (object arg)
431 return arg != null ? arg.ToString () : String.Empty;
434 public static string Concat (params object[] args)
441 throw new ArgumentNullException ();
443 strings = new string [args.Length];
446 foreach (object arg in args) {
447 /* use Empty for each null argument */
449 strings[i] = String.Empty;
451 strings[i] = arg.ToString ();
452 len += strings[i].length;
459 String res = new String (len);
462 for (int j = 0; j < strings.Length; j++)
463 for (int k = 0; k < strings[j].length; k++)
464 str[i++] = strings[j].c_str[k];
469 public static string Concat (params string[] values)
475 throw new ArgumentNullException ();
478 foreach (string value in values)
479 len += value != null ? value.Length : 0;
484 String res = new String (len);
487 foreach (string value in values) {
491 for (int j = 0; j < value.length; j++)
492 str[i++] = value.c_str[j];
498 public static string Concat (object arg0, object arg1)
500 string str0 = arg0 != null ? arg0.ToString () : String.Empty;
501 string str1 = arg1 != null ? arg1.ToString () : String.Empty;
503 return Concat (str0, str1);
506 public static string Concat (string str0, string str1)
516 len = str0.length + str1.length;
520 String res = new String (len);
523 for (i = 0; i < str0.length; i++)
524 concat[i] = str0.c_str[i];
525 for (j = 0 ; j < str1.length; j++)
526 concat[i + j] = str1.c_str[j];
531 public static string Concat (object arg0, object arg1, object arg2)
533 string str0 = arg0 != null ? arg0.ToString () : String.Empty;
534 string str1 = arg1 != null ? arg1.ToString () : String.Empty;
535 string str2 = arg2 != null ? arg2.ToString () : String.Empty;
537 return Concat (str0, str1, str2);
540 public static string Concat (string str0, string str1, string str2)
552 len = str0.length + str1.length + str2.length;
556 String res = new String (len);
559 for (i = 0; i < str0.length; i++)
560 concat[i] = str0.c_str[i];
561 for (j = 0; j < str1.length; j++)
562 concat[i + j] = str1.c_str[j];
563 for (k = 0; k < str2.length; k++)
564 concat[i + j + k] = str2.c_str[k];
569 public static string Concat (string str0, string str1, string str2, string str3)
583 len = str0.length + str1.length + str2.length + str3.length;
586 String res = new String (len);
589 for (i = 0; i < str0.length; i++)
590 concat[i] = str0.c_str[i];
591 for (j = 0; j < str1.length; j++)
592 concat[i + j] = str1.c_str[j];
593 for (k = 0; k < str2.length; k++)
594 concat[i + j + k] = str2.c_str[k];
595 for (l = 0; l < str3.length; l++)
596 concat[i + j + k + l] = str3.c_str[l];
601 public static string Copy (string str)
603 // FIXME: how do I *copy* a string if I can only have 1 of each?
605 throw new ArgumentNullException ();
610 public void CopyTo (int sourceIndex, char[] destination, int destinationIndex, int count)
612 // LAMESPEC: should I null-terminate?
615 if (destination == null)
616 throw new ArgumentNullException ();
618 if (sourceIndex < 0 || destinationIndex < 0 || count < 0)
619 throw new ArgumentOutOfRangeException ();
621 if (sourceIndex + count > this.length)
622 throw new ArgumentOutOfRangeException ();
624 if (destinationIndex + count > destination.Length)
625 throw new ArgumentOutOfRangeException ();
627 for (i = 0; i < count; i++)
628 destination[destinationIndex + i] = this.c_str[sourceIndex + i];
631 public bool EndsWith (string value)
633 bool endswith = true;
637 throw new ArgumentNullException ();
639 start = this.length - value.length;
643 for (i = start; i < this.length && endswith; i++)
644 endswith = this.c_str[i] == value.c_str[i - start];
649 public override bool Equals (object obj)
651 if (!(obj is String))
654 return this == (String) obj;
657 public bool Equals (string value)
659 return this == value;
662 public static bool Equals (string a, string b)
667 public static string Format (string format, object arg0) {
668 return Format (null, format, new object[] { arg0 });
671 public static string Format (string format, object arg0, object arg1) {
672 return Format (null, format, new object[] { arg0, arg1 });
675 public static string Format (string format, object arg0, object arg1, object arg2) {
676 return Format (null, format, new object[] { arg0, arg1, arg2 });
679 public static string Format (string format, params object[] args) {
680 return Format (null, format, args);
683 public static string Format (IFormatProvider provider, string format, params object[] args) {
684 if (format == null || args == null)
685 throw new ArgumentNullException ();
687 StringBuilder result = new StringBuilder ();
691 while (ptr < format.Length) {
692 char c = format[ptr ++];
695 result.Append (format, start, ptr - start - 1);
697 // check for escaped open bracket
699 if (format[ptr] == '{') {
710 ParseFormatSpecifier (format, ref ptr, out n, out width, out left_align, out arg_format);
711 if (n >= args.Length)
712 throw new FormatException ("Index (zero based) must be greater than or equal to zero and less than the size of the argument list.");
716 object arg = args[n];
721 else if (arg is IFormattable)
722 str = ((IFormattable)arg).ToString (arg_format, provider);
724 str = arg.ToString ();
726 // pad formatted string and append to result
728 if (width > str.Length) {
729 string pad = new String (' ', width - str.Length);
745 else if (c == '}' && format[ptr] == '}') {
746 result.Append (format, start, ptr - start - 1);
751 if (start < format.Length)
752 result.Append (format.Substring (start));
754 return result.ToString ();
757 public CharEnumerator GetEnumerator ()
759 return new CharEnumerator (this);
762 IEnumerator IEnumerable.GetEnumerator ()
764 return new CharEnumerator (this);
767 public override int GetHashCode ()
771 for (i = 0; i < length; ++i)
772 h = (h << 5) - h + c_str [i];
776 public TypeCode GetTypeCode ()
778 return TypeCode.String;
781 public int IndexOf (char value)
783 return IndexOf (value, 0, this.length);
786 public int IndexOf (string value)
788 return IndexOf (value, 0, this.length);
791 public int IndexOf (char value, int startIndex)
793 return IndexOf (value, startIndex, this.length - startIndex);
796 public int IndexOf (string value, int startIndex)
798 return IndexOf (value, startIndex, this.length - startIndex);
801 public int IndexOf (char value, int startIndex, int count)
805 if (startIndex < 0 || count < 0 || startIndex + count > this.length)
806 throw new ArgumentOutOfRangeException ();
808 for (i = startIndex; i - startIndex < count; i++)
809 if (this.c_str[i] == value)
815 public int IndexOf (string value, int startIndex, int count)
818 throw new ArgumentNullException ();
820 if (startIndex < 0 || count < 0 || startIndex + count > this.length)
821 throw new ArgumentOutOfRangeException ();
824 return BoyerMoore (this.c_str, value, startIndex, count);
827 for (i = startIndex; i - startIndex + value.length <= count; ) {
828 if (this.c_str[i] == value.c_str [0]) {
832 for (j = 1; equal && j < value.length; j++) {
833 equal = this.c_str[i + j] == value.c_str [j];
834 if (this.c_str [i + j] == value.c_str [0] && nexti == 0)
852 public int IndexOfAny (char[] values)
854 return IndexOfAny (values, 0, this.length);
857 public int IndexOfAny (char[] values, int startIndex)
859 return IndexOfAny (values, startIndex, this.length - startIndex);
862 [MethodImplAttribute(MethodImplOptions.InternalCall)]
863 internal extern int InternalIndexOfAny (char[] values, int startIndex, int count);
865 public int IndexOfAny (char[] values, int startIndex, int count)
868 throw new ArgumentNullException ();
870 if (startIndex < 0 || count < 0 || startIndex + count > this.length)
871 throw new ArgumentOutOfRangeException ();
873 return InternalIndexOfAny (values, startIndex, count);
876 public string Insert (int startIndex, string value)
882 throw new ArgumentNullException ();
884 if (startIndex < 0 || startIndex > this.length)
885 throw new ArgumentOutOfRangeException ();
887 String res = new String (value.length + this.length);
890 for (i = 0; i < startIndex; i++)
891 str[i] = this.c_str[i];
892 for (j = 0; j < value.length; j++)
893 str[i + j] = value.c_str[j];
894 for ( ; i < this.length; i++)
895 str[i + j] = this.c_str[i];
900 [MethodImplAttribute(MethodImplOptions.InternalCall)]
901 internal extern static string _Intern (string str);
903 public static string Intern (string str)
906 throw new ArgumentNullException ();
908 return _Intern (str);
911 [MethodImplAttribute(MethodImplOptions.InternalCall)]
912 internal extern static string _IsInterned (string str);
914 public static string IsInterned (string str)
917 throw new ArgumentNullException ();
919 return _IsInterned (str);
922 public static string Join (string separator, string[] value)
925 throw new ArgumentNullException ();
927 return Join (separator, value, 0, value.Length);
930 public static string Join (string separator, string[] value, int startIndex, int count)
932 // LAMESPEC: msdn doesn't specify what happens when separator is null
936 if (separator == null || value == null)
937 throw new ArgumentNullException ();
939 if (startIndex + count > value.Length)
940 throw new ArgumentOutOfRangeException ();
943 for (i = startIndex, used = 0; used < count; i++, used++) {
945 len += separator.length;
947 len += value[i].length;
950 // We have no elements to join?
954 String res = new String (len);
957 for (i = 0; i < value[startIndex].length; i++)
958 str[i] = value[startIndex][i];
961 for (j = startIndex + 1; used < count; j++, used++) {
964 for (k = 0; k < separator.length; k++)
965 str[i++] = separator.c_str[k];
966 for (k = 0; k < value[j].length; k++)
967 str[i++] = value[j].c_str[k];
973 public int LastIndexOf (char value)
979 for (; i >= 0; i--) {
980 if (this.c_str[i] == value)
987 public int LastIndexOf (string value)
990 throw new ArgumentNullException ();
991 if (value.length == 0)
993 if (this.length == 0)
996 return LastIndexOf (value, this.length - 1, this.length);
999 public int LastIndexOf (char value, int startIndex)
1001 if (startIndex < 0 || startIndex >= this.length)
1002 throw new ArgumentOutOfRangeException ();
1004 for (int i = startIndex; i >= 0; i--) {
1005 if (this.c_str[i] == value)
1012 public int LastIndexOf (string value, int startIndex)
1014 return LastIndexOf (value, startIndex, startIndex + 1);
1017 public int LastIndexOf (char value, int startIndex, int count)
1019 if (startIndex < 0 || count < 0)
1020 throw new ArgumentOutOfRangeException ();
1022 if (startIndex >= this.length || startIndex - count + 1 < 0)
1023 throw new ArgumentOutOfRangeException ();
1025 for (int i = startIndex; i > startIndex - count; i--) {
1026 if (this.c_str[i] == value)
1033 public int LastIndexOf (string value, int startIndex, int count)
1035 // startIndex points to the end of value, ie. we're searching backwards.
1039 throw new ArgumentNullException ();
1041 if (startIndex < 0 || startIndex > this.length)
1042 throw new ArgumentOutOfRangeException ();
1044 if (count < 0 || startIndex - count + 1 < 0)
1045 throw new ArgumentOutOfRangeException ();
1047 if (value.length > startIndex)
1050 if (value == String.Empty)
1053 if (startIndex == this.length)
1056 // FIXME: use a reversed-unicode-safe-Boyer-Moore?
1057 len = value.length - 1;
1058 for (i = startIndex; i > startIndex - count; i--) {
1060 if (this.c_str[i] == value.c_str[len]) {
1064 for (j = 0; equal && j < len; j++)
1065 equal = this.c_str[i - j] == value.c_str[len - j];
1075 public int LastIndexOfAny (char[] values)
1077 return LastIndexOfAny (values, this.length - 1, this.length);
1080 public int LastIndexOfAny (char[] values, int startIndex)
1082 return LastIndexOfAny (values, startIndex, startIndex + 1);
1085 public int LastIndexOfAny (char[] values, int startIndex, int count)
1090 throw new ArgumentNullException ();
1092 if (startIndex < 0 || count < 0 || startIndex - count + 1 < 0)
1093 throw new ArgumentOutOfRangeException ();
1095 for (i = startIndex; i > startIndex - count; i--) {
1096 for (int j = 0; j < strlen (values); j++) {
1097 if (this.c_str[i] == values[j])
1105 public string PadLeft (int totalWidth)
1107 return PadLeft (totalWidth, ' ');
1110 public string PadLeft (int totalWidth, char padChar)
1116 throw new ArgumentException ();
1118 str = new char [totalWidth > this.length ? totalWidth : this.length];
1119 for (i = 0; i < totalWidth - this.length; i++)
1122 for (j = 0; j < this.length; i++, j++)
1123 str[i] = this.c_str[j];
1125 return new String (str);
1128 public string PadRight (int totalWidth)
1130 return PadRight (totalWidth, ' ');
1133 public string PadRight (int totalWidth, char padChar)
1139 throw new ArgumentException ();
1141 str = new char [totalWidth > this.length ? totalWidth : this.length];
1142 for (i = 0; i < this.length; i++)
1143 str[i] = this.c_str[i];
1145 for ( ; i < str.Length; i++)
1148 return new String (str);
1151 public string Remove (int startIndex, int count)
1156 if (startIndex < 0 || count < 0 || startIndex + count > this.length)
1157 throw new ArgumentOutOfRangeException ();
1159 len = this.length - count;
1161 return String.Empty;
1163 String res = new String (len);
1165 for (i = 0; i < startIndex; i++)
1166 str[i] = this.c_str[i];
1167 for (j = i + count; j < this.length; j++)
1168 str[i++] = this.c_str[j];
1173 public string Replace (char oldChar, char newChar)
1178 String res = new String (length);
1180 for (i = 0; i < this.length; i++) {
1181 if (this.c_str[i] == oldChar)
1184 str[i] = this.c_str[i];
1190 public string Replace (string oldValue, string newValue)
1192 // If newValue is null, oldValue is removed.
1193 int index, len, i, j, newlen;
1197 if (oldValue == null)
1198 throw new ArgumentNullException ();
1200 thestring = Substring (0, this.length);
1203 // Runs until all occurences of oldValue have been replaced.
1205 // Use IndexOf in case I later rewrite it to use Boyer-Moore
1206 index = thestring.IndexOf (oldValue, index);
1211 newlen = (newValue == null) ? 0 : newValue.length;
1212 len = thestring.length - oldValue.length + newlen;
1215 return String.Empty;
1217 String res = new String (len);
1219 for (i = 0; i < index; i++)
1220 str[i] = thestring.c_str[i];
1221 for (j = 0; j < newlen; j++)
1222 str[i++] = newValue[j];
1223 for (j = index + oldValue.length; j < thestring.length; j++)
1224 str[i++] = thestring.c_str[j];
1226 // Increment index, we're already done replacing until this index.
1232 private int splitme (char[] separators, int startIndex)
1234 /* this is basically a customized IndexOfAny() for the Split() methods */
1235 for (int i = startIndex; i < this.length; i++) {
1236 if (separators != null) {
1237 foreach (char sep in separators) {
1238 if (this.c_str[i] == sep)
1239 return i - startIndex;
1241 } else if (is_lwsp (this.c_str[i])) {
1242 return i - startIndex;
1249 public string[] Split (params char[] separator)
1253 * @separator: delimiting chars or null to split on whtspc
1255 * Returns: 1. An array consisting of a single
1256 * element (@this) if none of the delimiting
1257 * chars appear in @this. 2. An array of
1258 * substrings which are delimited by one of
1259 * the separator chars. 3. An array of
1260 * substrings separated by whitespace if
1261 * @separator is null. The Empty string should
1262 * be returned wherever 2 delimiting chars are
1265 // FIXME: would using a Queue be better?
1270 list = new ArrayList ();
1271 for (index = 0, len = 0; index < this.length; index += len + 1) {
1272 len = splitme (separator, index);
1273 len = len > -1 ? len : this.length - index;
1275 list.Add (String.Empty);
1280 str = new char [len];
1281 for (i = 0; i < len; i++)
1282 str[i] = this.c_str[index + i];
1284 list.Add (new String (str));
1288 strings = new string [list.Count];
1289 if (list.Count == 1) {
1290 /* special case for an array holding @this */
1293 for (index = 0; index < list.Count; index++)
1294 strings[index] = (string) list[index];
1300 public string[] Split (char[] separator, int maxCount)
1302 // FIXME: would using Queue be better than ArrayList?
1305 int index, len, used;
1308 return new string[0];
1309 else if (maxCount < 0)
1310 throw new ArgumentOutOfRangeException ();
1313 list = new ArrayList ();
1314 for (index = 0, len = 0; index < this.length && used < maxCount; index += len + 1) {
1315 len = splitme (separator, index);
1316 len = len > -1 ? len : this.length - index;
1318 list.Add (String.Empty);
1323 str = new char [len];
1324 for (i = 0; i < len; i++)
1325 str[i] = this.c_str[index + i];
1327 list.Add (new String (str));
1332 /* fit the remaining chunk of the @this into it's own element */
1333 if (index <= this.length)
1338 str = new char [this.length - index];
1339 for (i = index; i < this.length; i++)
1340 str[i - index] = this.c_str[i];
1342 // maxCount cannot be zero if we reach this point and this means that
1343 // index can't be zero either.
1344 if (used == maxCount)
1345 list[used-1] += this.c_str[index-1] + new String (str);
1347 list.Add (new String (str));
1350 strings = new string [list.Count];
1351 if (list.Count == 1) {
1352 /* special case for an array holding @this */
1355 for (index = 0; index < list.Count; index++)
1356 strings[index] = (string) list[index];
1362 public bool StartsWith (string value)
1364 bool startswith = true;
1368 throw new ArgumentNullException ();
1370 if (value.length > this.length)
1373 for (i = 0; i < value.length && startswith; i++)
1374 startswith = startswith && value.c_str[i] == this.c_str[i];
1379 public string Substring (int startIndex)
1384 if (startIndex < 0 || startIndex > this.length)
1385 throw new ArgumentOutOfRangeException ();
1387 len = this.length - startIndex;
1389 return String.Empty;
1390 String res = new String (len);
1392 for (i = startIndex; i < this.length; i++)
1393 str[i - startIndex] = this.c_str[i];
1398 public string Substring (int startIndex, int length)
1403 if (startIndex < 0 || length < 0 || startIndex + length > this.length)
1404 throw new ArgumentOutOfRangeException ();
1407 return String.Empty;
1408 String res = new String (length);
1411 // NOTE: Not all the characters are adjacent at the beginning of the
1412 // array. StringBuilder.Append() will place characters at the end
1413 // of the array, potentially leaving null characters in between.
1415 for (i = startIndex; i < this.length && CopyCount < length; i++){
1416 // Don't copy null characters
1417 if ((int)this.c_str[i] != 0) {
1418 str[CopyCount] = this.c_str[i];
1425 bool IConvertible.ToBoolean (IFormatProvider provider)
1427 return Convert.ToBoolean (this);
1430 byte IConvertible.ToByte (IFormatProvider provider)
1432 return Convert.ToByte (this);
1435 char IConvertible.ToChar (IFormatProvider provider)
1437 return Convert.ToChar (this);
1440 public char[] ToCharArray ()
1442 return ToCharArray (0, this.length);
1445 public char[] ToCharArray (int startIndex, int length)
1450 if (startIndex < 0 || length < 0 || startIndex + length > this.length)
1451 throw new ArgumentOutOfRangeException ();
1453 chars = new char [length];
1454 for (i = startIndex; i < length; i++)
1455 chars[i - startIndex] = this.c_str[i];
1460 DateTime IConvertible.ToDateTime (IFormatProvider provider)
1462 return Convert.ToDateTime (this);
1465 decimal IConvertible.ToDecimal (IFormatProvider provider)
1467 return Convert.ToDecimal (this);
1470 double IConvertible.ToDouble (IFormatProvider provider)
1472 return Convert.ToDouble (this);
1475 short IConvertible.ToInt16 (IFormatProvider provider)
1477 return Convert.ToInt16 (this);
1480 int IConvertible.ToInt32 (IFormatProvider provider)
1482 return Convert.ToInt32 (this);
1485 long IConvertible.ToInt64 (IFormatProvider provider)
1487 return Convert.ToInt64 (this);
1490 public string ToLower ()
1495 String res = new String (length);
1497 for (i = 0; i < this.length; i++)
1498 str[i] = Char.ToLower (this.c_str[i]);
1504 public string ToLower (CultureInfo culture)
1506 // FIXME: implement me
1507 throw new NotImplementedException ();
1511 [CLSCompliant(false)]
1512 sbyte IConvertible.ToSByte (IFormatProvider provider)
1514 return Convert.ToSByte (this);
1517 float IConvertible.ToSingle (IFormatProvider provider)
1519 return Convert.ToSingle (this);
1522 public override string ToString ()
1527 string IConvertible.ToString (IFormatProvider format)
1532 object IConvertible.ToType (Type conversionType, IFormatProvider provider)
1534 return Convert.ToType (this, conversionType, provider);
1537 [CLSCompliant(false)]
1538 ushort IConvertible.ToUInt16 (IFormatProvider provider)
1540 return Convert.ToUInt16 (this);
1543 [CLSCompliant(false)]
1544 uint IConvertible.ToUInt32 (IFormatProvider provider)
1546 return Convert.ToUInt32 (this);
1549 [CLSCompliant(false)]
1550 ulong IConvertible.ToUInt64 (IFormatProvider provider)
1552 return Convert.ToUInt64 (this);
1555 public string ToUpper ()
1560 String res = new String (length);
1562 for (i = 0; i < this.length; i++)
1563 str[i] = Char.ToUpper (this.c_str[i]);
1569 public string ToUpper (CultureInfo culture)
1571 // FIXME: implement me
1572 throw new NotImplementedException ();
1575 public string Trim ()
1580 public string Trim (params char[] trimChars)
1583 bool matches = false;
1585 for (begin = 0; begin < this.length; begin++) {
1586 if (trimChars != null) {
1588 foreach (char c in trimChars) {
1589 matches = this.c_str[begin] == c;
1596 matches = is_lwsp (this.c_str[begin]);
1603 for (end = this.length - 1; end > begin; end--) {
1604 if (trimChars != null) {
1606 foreach (char c in trimChars) {
1607 matches = this.c_str[end] == c;
1614 matches = is_lwsp (this.c_str[end]);
1623 return String.Empty;
1625 return Substring (begin, end - begin);
1628 public string TrimEnd (params char[] trimChars)
1630 bool matches = true;
1633 for (end = this.length - 1; matches && end > 0; end--) {
1635 if (trimChars != null) {
1637 foreach (char c in trimChars) {
1638 matches = this.c_str[end] == c;
1643 matches = is_lwsp (this.c_str[end]);
1647 return Substring (0, end+1);
1651 return String.Empty;
1653 return Substring (0, end);
1656 public string TrimStart (params char[] trimChars)
1658 bool matches = true;
1661 for (begin = 0; matches && begin < this.length; begin++) {
1662 if (trimChars != null) {
1664 foreach (char c in trimChars) {
1665 matches = this.c_str[begin] == c;
1670 matches = is_lwsp (this.c_str[begin]);
1674 return Substring (begin, this.length - begin);
1677 return String.Empty;
1681 public static bool operator ==(string a, string b)
1683 if ((object)a == null) {
1684 if ((object)b == null)
1688 if ((object)b == null)
1691 if (a.length != b.length)
1695 for (int i = 0; i < l; i++)
1696 if (a.c_str[i] != b.c_str[i])
1702 public static bool operator !=(string a, string b)
1709 private static void ParseFormatSpecifier (string str, ref int ptr, out int n, out int width, out bool left_align, out string format) {
1710 // parses format specifier of form:
1716 // N = argument number (non-negative integer)
1718 n = ParseDecimal (str, ref ptr);
1720 throw new FormatException ("Input string was not in correct format.");
1722 // M = width (non-negative integer)
1724 if (str[ptr] == ',') {
1725 left_align = (str[++ ptr] == '-');
1729 width = ParseDecimal (str, ref ptr);
1731 throw new FormatException ("Input string was not in correct format.");
1738 // F = argument format (string)
1740 if (str[ptr] == ':') {
1742 while (str[ptr] != '}')
1745 format = str.Substring (start, ptr - start);
1750 if (str[ptr ++] != '}')
1751 throw new FormatException ("Input string was not in correct format.");
1753 catch (IndexOutOfRangeException) {
1754 throw new FormatException ("Input string was not in correct format.");
1758 private static int ParseDecimal (string str, ref int ptr) {
1763 if (c < '0' || '9' < c)
1766 n = n * 10 + c - '0';