Support SMTP URLs
[zxing.git] / core / src / com / google / zxing / client / result / ResultParser.java
1 /*
2  * Copyright 2007 ZXing authors
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 com.google.zxing.client.result;
18
19 import com.google.zxing.Result;
20
21 import java.util.Hashtable;
22 import java.util.Vector;
23
24 /**
25  * <p>Abstract class representing the result of decoding a barcode, as more than
26  * a String -- as some type of structured data. This might be a subclass which represents
27  * a URL, or an e-mail address. {@link #parseResult(com.google.zxing.Result)} will turn a raw
28  * decoded string into the most appropriate type of structured representation.</p>
29  *
30  * <p>Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
31  * on exception-based mechanisms during parsing.</p>
32  *
33  * @author Sean Owen
34  */
35 public abstract class ResultParser {
36
37   public static ParsedResult parseResult(Result theResult) {
38     // This is a bit messy, but given limited options in MIDP / CLDC, this may well be the simplest
39     // way to go about this. For example, we have no reflection available, really.
40     // Order is important here.
41     ParsedResult result;
42     if ((result = BookmarkDoCoMoResultParser.parse(theResult)) != null) {
43       return result;
44     } else if ((result = AddressBookDoCoMoResultParser.parse(theResult)) != null) {
45       return result;
46     } else if ((result = EmailDoCoMoResultParser.parse(theResult)) != null) {
47       return result;
48     } else if ((result = AddressBookAUResultParser.parse(theResult)) != null) {
49       return result;
50     } else if ((result = VCardResultParser.parse(theResult)) != null) {
51       return result;
52     } else if ((result = BizcardResultParser.parse(theResult)) != null) {
53       return result;
54     } else if ((result = VEventResultParser.parse(theResult)) != null) {
55       return result;
56     } else if ((result = EmailAddressResultParser.parse(theResult)) != null) {
57       return result;
58     } else if ((result = SMTPResultParser.parse(theResult)) != null) {
59       return result;
60     } else if ((result = TelResultParser.parse(theResult)) != null) {
61       return result;
62     } else if ((result = SMSMMSResultParser.parse(theResult)) != null) {
63       return result;
64     } else if ((result = SMSTOMMSTOResultParser.parse(theResult)) != null) {
65       return result;
66     } else if ((result = GeoResultParser.parse(theResult)) != null) {
67       return result;
68     } else if ((result = WifiResultParser.parse(theResult)) != null) {
69       return result;
70     } else if ((result = URLTOResultParser.parse(theResult)) != null) {
71       return result;
72     } else if ((result = URIResultParser.parse(theResult)) != null) {
73       return result;
74     } else if ((result = ISBNResultParser.parse(theResult)) != null) {
75       // We depend on ISBN parsing coming before UPC, as it is a subset.
76       return result;
77     } else if ((result = ProductResultParser.parse(theResult)) != null) {
78       return result;
79     } else if ((result = ExpandedProductResultParser.parse(theResult)) != null) {
80       return result;
81     }
82     return new TextParsedResult(theResult.getText(), null);
83   }
84
85   protected static void maybeAppend(String value, StringBuffer result) {
86     if (value != null) {
87       result.append('\n');
88       result.append(value);
89     }
90   }
91
92   protected static void maybeAppend(String[] value, StringBuffer result) {
93     if (value != null) {
94       for (int i = 0; i < value.length; i++) {
95         result.append('\n');
96         result.append(value[i]);
97       }
98     }
99   }
100
101   protected static String[] maybeWrap(String value) {
102     return value == null ? null : new String[] { value };
103   }
104
105   protected static String unescapeBackslash(String escaped) {
106     if (escaped != null) {
107       int backslash = escaped.indexOf((int) '\\');
108       if (backslash >= 0) {
109         int max = escaped.length();
110         StringBuffer unescaped = new StringBuffer(max - 1);
111         unescaped.append(escaped.toCharArray(), 0, backslash);
112         boolean nextIsEscaped = false;
113         for (int i = backslash; i < max; i++) {
114           char c = escaped.charAt(i);
115           if (nextIsEscaped || c != '\\') {
116             unescaped.append(c);
117             nextIsEscaped = false;
118           } else {
119             nextIsEscaped = true;
120           }
121         }
122         return unescaped.toString();
123       }
124     }
125     return escaped;
126   }
127
128   private static String urlDecode(String escaped) {
129
130     // No we can't use java.net.URLDecoder here. JavaME doesn't have it.
131     if (escaped == null) {
132       return null;
133     }
134     char[] escapedArray = escaped.toCharArray();
135
136     int first = findFirstEscape(escapedArray);
137     if (first < 0) {
138       return escaped;
139     }
140
141     int max = escapedArray.length;
142     // final length is at most 2 less than original due to at least 1 unescaping
143     StringBuffer unescaped = new StringBuffer(max - 2);
144     // Can append everything up to first escape character
145     unescaped.append(escapedArray, 0, first);
146
147     for (int i = first; i < max; i++) {
148       char c = escapedArray[i];
149       if (c == '+') {
150         // + is translated directly into a space
151         unescaped.append(' ');
152       } else if (c == '%') {
153         // Are there even two more chars? if not we will just copy the escaped sequence and be done
154         if (i >= max - 2) {
155           unescaped.append('%'); // append that % and move on
156         } else {
157           int firstDigitValue = parseHexDigit(escapedArray[++i]);
158           int secondDigitValue = parseHexDigit(escapedArray[++i]);
159           if (firstDigitValue < 0 || secondDigitValue < 0) {
160             // bad digit, just move on
161             unescaped.append('%');
162             unescaped.append(escapedArray[i-1]);
163             unescaped.append(escapedArray[i]);
164           }
165           unescaped.append((char) ((firstDigitValue << 4) + secondDigitValue));
166         }
167       } else {
168         unescaped.append(c);
169       }
170     }
171     return unescaped.toString();
172   }
173
174   private static int findFirstEscape(char[] escapedArray) {
175     int max = escapedArray.length;
176     for (int i = 0; i < max; i++) {
177       char c = escapedArray[i];
178       if (c == '+' || c == '%') {
179         return i;
180       }
181     }
182     return -1;
183   }
184
185   private static int parseHexDigit(char c) {
186     if (c >= 'a') {
187       if (c <= 'f') {
188         return 10 + (c - 'a');
189       }
190     } else if (c >= 'A') {
191       if (c <= 'F') {
192         return 10 + (c - 'A');
193       }
194     } else if (c >= '0') {
195       if (c <= '9') {
196         return c - '0';
197       }
198     }
199     return -1;
200   }
201
202   protected static boolean isStringOfDigits(String value, int length) {
203     if (value == null) {
204       return false;
205     }
206     int stringLength = value.length();
207     if (length != stringLength) {
208       return false;
209     }
210     for (int i = 0; i < length; i++) {
211       char c = value.charAt(i);
212       if (c < '0' || c > '9') {
213         return false;
214       }
215     }
216     return true;
217   }
218
219   protected static boolean isSubstringOfDigits(String value, int offset, int length) {
220     if (value == null) {
221       return false;
222     }
223     int stringLength = value.length();
224     int max = offset + length;
225     if (stringLength < max) {
226       return false;
227     }
228     for (int i = offset; i < max; i++) {
229       char c = value.charAt(i);
230       if (c < '0' || c > '9') {
231         return false;
232       }
233     }
234     return true;
235   }
236
237   static Hashtable parseNameValuePairs(String uri) {
238     int paramStart = uri.indexOf('?');
239     if (paramStart < 0) {
240       return null;
241     }
242     Hashtable result = new Hashtable(3);
243     paramStart++;
244     int paramEnd;
245     while ((paramEnd = uri.indexOf('&', paramStart)) >= 0) {
246       appendKeyValue(uri, paramStart, paramEnd, result);
247       paramStart = paramEnd + 1;
248     }
249     appendKeyValue(uri, paramStart, uri.length(), result);
250     return result;
251   }
252
253   private static void appendKeyValue(String uri, int paramStart, int paramEnd, Hashtable result) {
254     int separator = uri.indexOf('=', paramStart);
255     if (separator >= 0) {
256       // key = value
257       String key = uri.substring(paramStart, separator);
258       String value = uri.substring(separator + 1, paramEnd);
259       value = urlDecode(value);
260       result.put(key, value);
261     }
262     // Can't put key, null into a hashtable
263   }
264
265   static String[] matchPrefixedField(String prefix, String rawText, char endChar, boolean trim) {
266     Vector matches = null;
267     int i = 0;
268     int max = rawText.length();
269     while (i < max) {
270       i = rawText.indexOf(prefix, i);
271       if (i < 0) {
272         break;
273       }
274       i += prefix.length(); // Skip past this prefix we found to start
275       int start = i; // Found the start of a match here
276       boolean done = false;
277       while (!done) {
278         i = rawText.indexOf((int) endChar, i);
279         if (i < 0) {
280           // No terminating end character? uh, done. Set i such that loop terminates and break
281           i = rawText.length();
282           done = true;
283         } else if (rawText.charAt(i - 1) == '\\') {
284           // semicolon was escaped so continue
285           i++;
286         } else {
287           // found a match
288           if (matches == null) {
289             matches = new Vector(3); // lazy init
290           }
291           String element = unescapeBackslash(rawText.substring(start, i));
292           if (trim) {
293             element = element.trim();
294           }
295           matches.addElement(element);
296           i++;
297           done = true;
298         }
299       }
300     }
301     if (matches == null || matches.isEmpty()) {
302       return null;
303     }
304     return toStringArray(matches);
305   }
306
307   static String matchSinglePrefixedField(String prefix, String rawText, char endChar, boolean trim) {
308     String[] matches = matchPrefixedField(prefix, rawText, endChar, trim);
309     return matches == null ? null : matches[0];
310   }
311
312   static String[] toStringArray(Vector strings) {
313     int size = strings.size();
314     String[] result = new String[size];
315     for (int j = 0; j < size; j++) {
316       result[j] = (String) strings.elementAt(j);
317     }
318     return result;
319   }
320
321 }