• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.text.util;
18 
19 import android.widget.MultiAutoCompleteTextView;
20 
21 import java.util.ArrayList;
22 import java.util.Collection;
23 
24 /**
25  * This class works as a Tokenizer for MultiAutoCompleteTextView for
26  * address list fields, and also provides a method for converting
27  * a string of addresses (such as might be typed into such a field)
28  * into a series of Rfc822Tokens.
29  */
30 @android.ravenwood.annotation.RavenwoodKeepWholeClass
31 public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer {
32 
33     /**
34      * This constructor will try to take a string like
35      * "Foo Bar (something) <foo\@google.com>,
36      * blah\@google.com (something)"
37      * and convert it into one or more Rfc822Tokens, output into the supplied
38      * collection.
39      *
40      * It does *not* decode MIME encoded-words; charset conversion
41      * must already have taken place if necessary.
42      * It will try to be tolerant of broken syntax instead of
43      * returning an error.
44      *
45      */
tokenize(CharSequence text, Collection<Rfc822Token> out)46     public static void tokenize(CharSequence text, Collection<Rfc822Token> out) {
47         StringBuilder name = new StringBuilder();
48         StringBuilder address = new StringBuilder();
49         StringBuilder comment = new StringBuilder();
50 
51         int i = 0;
52         int cursor = text.length();
53 
54         while (i < cursor) {
55             char c = text.charAt(i);
56 
57             if (c == ',' || c == ';') {
58                 i++;
59 
60                 while (i < cursor && text.charAt(i) == ' ') {
61                     i++;
62                 }
63 
64                 crunch(name);
65 
66                 if (address.length() > 0) {
67                     out.add(new Rfc822Token(name.toString(),
68                                             address.toString(),
69                                             comment.toString()));
70                 } else if (name.length() > 0) {
71                     out.add(new Rfc822Token(null,
72                                             name.toString(),
73                                             comment.toString()));
74                 }
75 
76                 name.setLength(0);
77                 address.setLength(0);
78                 comment.setLength(0);
79             } else if (c == '"') {
80                 i++;
81 
82                 while (i < cursor) {
83                     c = text.charAt(i);
84 
85                     if (c == '"') {
86                         i++;
87                         break;
88                     } else if (c == '\\') {
89                         if (i + 1 < cursor) {
90                             name.append(text.charAt(i + 1));
91                         }
92                         i += 2;
93                     } else {
94                         name.append(c);
95                         i++;
96                     }
97                 }
98             } else if (c == '(') {
99                 int level = 1;
100                 i++;
101 
102                 while (i < cursor && level > 0) {
103                     c = text.charAt(i);
104 
105                     if (c == ')') {
106                         if (level > 1) {
107                             comment.append(c);
108                         }
109 
110                         level--;
111                         i++;
112                     } else if (c == '(') {
113                         comment.append(c);
114                         level++;
115                         i++;
116                     } else if (c == '\\') {
117                         if (i + 1 < cursor) {
118                             comment.append(text.charAt(i + 1));
119                         }
120                         i += 2;
121                     } else {
122                         comment.append(c);
123                         i++;
124                     }
125                 }
126             } else if (c == '<') {
127                 i++;
128 
129                 while (i < cursor) {
130                     c = text.charAt(i);
131 
132                     if (c == '>') {
133                         i++;
134                         break;
135                     } else {
136                         address.append(c);
137                         i++;
138                     }
139                 }
140             } else if (c == ' ') {
141                 name.append('\0');
142                 i++;
143             } else {
144                 name.append(c);
145                 i++;
146             }
147         }
148 
149         crunch(name);
150 
151         if (address.length() > 0) {
152             out.add(new Rfc822Token(name.toString(),
153                                     address.toString(),
154                                     comment.toString()));
155         } else if (name.length() > 0) {
156             out.add(new Rfc822Token(null,
157                                     name.toString(),
158                                     comment.toString()));
159         }
160     }
161 
162     /**
163      * This method will try to take a string like
164      * "Foo Bar (something) &lt;foo\@google.com&gt;,
165      * blah\@google.com (something)"
166      * and convert it into one or more Rfc822Tokens.
167      * It does *not* decode MIME encoded-words; charset conversion
168      * must already have taken place if necessary.
169      * It will try to be tolerant of broken syntax instead of
170      * returning an error.
171      */
tokenize(CharSequence text)172     public static Rfc822Token[] tokenize(CharSequence text) {
173         ArrayList<Rfc822Token> out = new ArrayList<Rfc822Token>();
174         tokenize(text, out);
175         return out.toArray(new Rfc822Token[out.size()]);
176     }
177 
crunch(StringBuilder sb)178     private static void crunch(StringBuilder sb) {
179         int i = 0;
180         int len = sb.length();
181 
182         while (i < len) {
183             char c = sb.charAt(i);
184 
185             if (c == '\0') {
186                 if (i == 0 || i == len - 1 ||
187                     sb.charAt(i - 1) == ' ' ||
188                     sb.charAt(i - 1) == '\0' ||
189                     sb.charAt(i + 1) == ' ' ||
190                     sb.charAt(i + 1) == '\0') {
191                     sb.deleteCharAt(i);
192                     len--;
193                 } else {
194                     i++;
195                 }
196             } else {
197                 i++;
198             }
199         }
200 
201         for (i = 0; i < len; i++) {
202             if (sb.charAt(i) == '\0') {
203                 sb.setCharAt(i, ' ');
204             }
205         }
206     }
207 
208     /**
209      * {@inheritDoc}
210      */
findTokenStart(CharSequence text, int cursor)211     public int findTokenStart(CharSequence text, int cursor) {
212         /*
213          * It's hard to search backward, so search forward until
214          * we reach the cursor.
215          */
216 
217         int best = 0;
218         int i = 0;
219 
220         while (i < cursor) {
221             i = findTokenEnd(text, i);
222 
223             if (i < cursor) {
224                 i++; // Skip terminating punctuation
225 
226                 while (i < cursor && text.charAt(i) == ' ') {
227                     i++;
228                 }
229 
230                 if (i < cursor) {
231                     best = i;
232                 }
233             }
234         }
235 
236         return best;
237     }
238 
239     /**
240      * {@inheritDoc}
241      */
findTokenEnd(CharSequence text, int cursor)242     public int findTokenEnd(CharSequence text, int cursor) {
243         int len = text.length();
244         int i = cursor;
245 
246         while (i < len) {
247             char c = text.charAt(i);
248 
249             if (c == ',' || c == ';') {
250                 return i;
251             } else if (c == '"') {
252                 i++;
253 
254                 while (i < len) {
255                     c = text.charAt(i);
256 
257                     if (c == '"') {
258                         i++;
259                         break;
260                     } else if (c == '\\' && i + 1 < len) {
261                         i += 2;
262                     } else {
263                         i++;
264                     }
265                 }
266             } else if (c == '(') {
267                 int level = 1;
268                 i++;
269 
270                 while (i < len && level > 0) {
271                     c = text.charAt(i);
272 
273                     if (c == ')') {
274                         level--;
275                         i++;
276                     } else if (c == '(') {
277                         level++;
278                         i++;
279                     } else if (c == '\\' && i + 1 < len) {
280                         i += 2;
281                     } else {
282                         i++;
283                     }
284                 }
285             } else if (c == '<') {
286                 i++;
287 
288                 while (i < len) {
289                     c = text.charAt(i);
290 
291                     if (c == '>') {
292                         i++;
293                         break;
294                     } else {
295                         i++;
296                     }
297                 }
298             } else {
299                 i++;
300             }
301         }
302 
303         return i;
304     }
305 
306     /**
307      * Terminates the specified address with a comma and space.
308      * This assumes that the specified text already has valid syntax.
309      * The Adapter subclass's convertToString() method must make that
310      * guarantee.
311      */
terminateToken(CharSequence text)312     public CharSequence terminateToken(CharSequence text) {
313         return text + ", ";
314     }
315 }
316