Merge pull request #1508 from slluis/fix-20966
[mono.git] / mcs / class / corlib / System.Globalization / StringInfo.cs
1 //
2 // System.Globalization.StringInfo.cs
3 //
4 // Author:
5 //      Dick Porter (dick@ximian.com)
6 //
7 // (C) 2002 Ximian, Inc.
8 // (C) 2004 Novell, Inc.
9 //
10
11 //
12 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 // 
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 // 
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33
34 using System.Collections;
35 using System.Runtime.InteropServices;
36
37 namespace System.Globalization {
38
39         [Serializable]
40         [ComVisible(true)]
41         public class StringInfo {
42
43                 public StringInfo () : this (string.Empty)
44                 {
45                 }
46
47                 string s;
48                 int length;
49
50                 public StringInfo (string value)
51                 {
52                         // Argument check in property
53                         String = value;
54                 }
55
56                 [ComVisible (false)]
57                 public override bool Equals (object value)
58                 {
59                         StringInfo other = value as StringInfo;
60                         return other != null && s == other.s;
61                 }
62
63                 [ComVisible (false)]
64                 public override int GetHashCode ()
65                 {
66                         return s.GetHashCode ();
67                 }
68
69                 public int LengthInTextElements {
70                         get {
71                                 if (length < 0) {
72                                         length = 0;
73                                         for (int idx = 0; idx < s.Length; length++)
74                                                 idx += GetNextTextElementLength (s, idx);
75                                 }
76                                 return length;
77                         }
78                 }
79
80                 public string String {
81                         get { return s; }
82                         set {
83                                 if (value == null)
84                                         throw new ArgumentNullException ("value");
85                                 length = -1;
86                                 s = value;
87                         }
88                 }
89
90                 public string SubstringByTextElements (int startingTextElement)
91                 {
92                         if (startingTextElement < 0 || s.Length == 0)
93                                 throw new ArgumentOutOfRangeException ("startingTextElement");
94                         int idx = 0;
95                         for (int i = 0; i < startingTextElement; i++) {
96                                 if (idx >= s.Length)
97                                         throw new ArgumentOutOfRangeException ("startingTextElement");
98                                 idx += GetNextTextElementLength (s, idx);
99                         }
100                         return s.Substring (idx);
101                 }
102
103                 public string SubstringByTextElements (int startingTextElement, int lengthInTextElements)
104                 {
105                         if (startingTextElement < 0 || s.Length == 0)
106                                 throw new ArgumentOutOfRangeException ("startingTextElement");
107                         if (lengthInTextElements < 0)
108                                 throw new ArgumentOutOfRangeException ("lengthInTextElements");
109                         int idx = 0;
110                         for (int i = 0; i < startingTextElement; i++) {
111                                 if (idx >= s.Length)
112                                         throw new ArgumentOutOfRangeException ("startingTextElement");
113                                 idx += GetNextTextElementLength (s, idx);
114                         }
115                         int start = idx;
116                         for (int i = 0; i < lengthInTextElements; i++) {
117                                 if (idx >= s.Length)
118                                         throw new ArgumentOutOfRangeException ("lengthInTextElements");
119                                 idx += GetNextTextElementLength (s, idx);
120                         }
121                         return s.Substring (start, idx - start);
122                 }
123
124                 public static string GetNextTextElement(string str)
125                 {
126                         if(str == null || str.Length == 0) {
127                                 throw new ArgumentNullException("string is null");
128                         }
129                         return(GetNextTextElement (str, 0));
130                 }
131
132                 public static string GetNextTextElement(string str, int index)
133                 {
134                         int len = GetNextTextElementLength (str, index);
135                         return len != 1 ? str.Substring (index, len) : new string (str [index], 1);
136                 }
137                 
138                 static int GetNextTextElementLength(string str, int index)
139                 {
140                         if(str == null) {
141                                 throw new ArgumentNullException("string is null");
142                         }
143
144                         if(index >= str.Length)
145                                 return 0;
146                         if(index < 0)
147                                 throw new ArgumentOutOfRangeException ("Index is not valid");
148
149                         /* Find the next base character, surrogate
150                          * pair or combining character sequence
151                          */
152
153                         char ch = str[index];
154                         UnicodeCategory cat = char.GetUnicodeCategory (ch);
155
156                         if (cat == UnicodeCategory.Surrogate) {
157                                 /* Check that it's a high surrogate
158                                  * followed by a low surrogate
159                                  */
160                                 if (ch >= 0xD800 && ch <= 0xDBFF) {
161                                         if ((index + 1) < str.Length &&
162                                             str[index + 1] >= 0xDC00 &&
163                                             str[index + 1] <= 0xDFFF) {
164                                                 /* A valid surrogate pair */
165                                                 return 2;
166                                         } else {
167                                                 /* High surrogate on its own */
168                                                 return 1;
169                                         }
170                                 } else {
171                                         /* Low surrogate on its own */
172                                         return 1;
173                                 }
174                         } else {
175                                 /* Look for a base character, which
176                                  * may or may not be followed by a
177                                  * series of combining characters
178                                  */
179
180                                 if (cat == UnicodeCategory.NonSpacingMark ||
181                                     cat == UnicodeCategory.SpacingCombiningMark ||
182                                     cat == UnicodeCategory.EnclosingMark) {
183                                         /* Not a base character */
184                                         return 1;
185                                 }
186                                 
187                                 int count = 1;
188
189                                 while (index + count < str.Length) {
190                                         cat = char.GetUnicodeCategory (str[index + count]);
191                                         if (cat != UnicodeCategory.NonSpacingMark &&
192                                             cat != UnicodeCategory.SpacingCombiningMark &&
193                                             cat != UnicodeCategory.EnclosingMark) {
194                                                 /* Finished the sequence */
195                                                 break;
196                                         }
197                                         count++;
198                                 }
199
200                                 return count;
201                         }
202                 }
203
204                 public static TextElementEnumerator GetTextElementEnumerator(string str)
205                 {
206                         if(str == null || str.Length == 0) {
207                                 throw new ArgumentNullException("string is null");
208                         }
209                         return(new TextElementEnumerator (str, 0));
210                 }
211
212                 public static TextElementEnumerator GetTextElementEnumerator(string str, int index)
213                 {
214                         if(str == null) {
215                                 throw new ArgumentNullException("string is null");
216                         }
217
218                         if(index < 0 || index >= str.Length) {
219                                 throw new ArgumentOutOfRangeException ("Index is not valid");
220                         }
221                         
222                         return(new TextElementEnumerator (str, index));
223                 }
224                 
225                 public static int[] ParseCombiningCharacters(string str)
226                 {
227                         if(str == null) {
228                                 throw new ArgumentNullException("string is null");
229                         }
230
231                         ArrayList indices = new ArrayList (str.Length);
232                         TextElementEnumerator tee = GetTextElementEnumerator (str);
233
234                         tee.Reset ();
235                         while(tee.MoveNext ()) {
236                                 indices.Add (tee.ElementIndex);
237                         }
238
239                         return((int[])indices.ToArray (typeof (int)));
240                 }
241         }
242 }