More refactoring of parsed results / result parsers; added basic vCard support
[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 srowen@google.com (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 = EmailAddressResultParser.parse(theResult)) != null) {
49       return result;
50     } else if ((result = AddressBookAUResultParser.parse(theResult)) != null) {
51       return result;
52     } else if ((result = VCardResultParser.parse(theResult)) != null) {
53       return result;
54     } else if ((result = TelResultParser.parse(theResult)) != null) {
55       return result;
56     } else if ((result = SMSResultParser.parse(theResult)) != null) {
57       return result;
58     } else if ((result = SMSTOResultParser.parse(theResult)) != null) {
59       return result;
60     } else if ((result = GeoResultParser.parse(theResult)) != null) {
61       return result;
62     } else if ((result = URLTOResultParser.parse(theResult)) != null) {
63       return result;
64     } else if ((result = URIResultParser.parse(theResult)) != null) {
65       return result;
66     } else if ((result = UPCResultParser.parse(theResult)) != null) {
67       return result;
68     }
69     return new TextParsedResult(theResult.getText(), null);
70   }
71
72   protected static void maybeAppend(String value, StringBuffer result) {
73     if (value != null) {
74       result.append('\n');
75       result.append(value);
76     }
77   }
78
79   protected static void maybeAppend(String[] value, StringBuffer result) {
80     if (value != null) {
81       for (int i = 0; i < value.length; i++) {
82         result.append('\n');
83         result.append(value[i]);
84       }
85     }
86   }
87
88   protected static String unescapeBackslash(String escaped) {
89     if (escaped != null) {
90       int backslash = escaped.indexOf((int) '\\');
91       if (backslash >= 0) {
92         int max = escaped.length();
93         StringBuffer unescaped = new StringBuffer(max - 1);
94         unescaped.append(escaped.toCharArray(), 0, backslash);
95         boolean nextIsEscaped = false;
96         for (int i = backslash; i < max; i++) {
97           char c = escaped.charAt(i);
98           if (nextIsEscaped || c != '\\') {
99             unescaped.append(c);
100             nextIsEscaped = false;
101           } else {
102             nextIsEscaped = true;
103           }
104         }
105         return unescaped.toString();
106       }
107     }
108     return escaped;
109   }
110
111   protected static String urlDecode(String escaped) {
112
113     // No we can't use java.net.URLDecoder here. JavaME doesn't have it.
114     if (escaped == null) {
115       return null;
116     }
117     char[] escapedArray = escaped.toCharArray();
118
119     int first = findFirstEscape(escapedArray);
120     if (first < 0) {
121       return escaped;
122     }
123
124     int max = escapedArray.length;
125     // final length is at most 2 less than original due to at least 1 unescaping
126     StringBuffer unescaped = new StringBuffer(max - 2);
127     // Can append everything up to first escape character
128     unescaped.append(escapedArray, 0, first);
129
130     for (int i = first; i < max; i++) {
131       char c = escapedArray[i];
132       if (c == '+') {
133         // + is translated directly into a space
134         unescaped.append(' ');
135       } else if (c == '%') {
136         // Are there even two more chars? if not we will just copy the escaped sequence and be done
137         if (i >= max - 2) {
138           unescaped.append('%'); // append that % and move on
139         } else {
140           int firstDigitValue = parseHexDigit(escapedArray[++i]);
141           int secondDigitValue = parseHexDigit(escapedArray[++i]);
142           if (firstDigitValue < 0 || secondDigitValue < 0) {
143             // bad digit, just move on
144             unescaped.append('%');
145             unescaped.append(escapedArray[i-1]);
146             unescaped.append(escapedArray[i]);
147           }
148           unescaped.append((char) ((firstDigitValue << 4) + secondDigitValue));
149         }
150       } else {
151         unescaped.append(c);
152       }
153     }
154     return unescaped.toString();
155   }
156
157   private static int findFirstEscape(char[] escapedArray) {
158     int max = escapedArray.length;
159     for (int i = 0; i < max; i++) {
160       char c = escapedArray[i];
161       if (c == '+' || c == '%') {
162         return i;
163       }
164     }
165     return -1;
166   }
167
168   private static int parseHexDigit(char c) {
169     if (c >= 'a') {
170       if (c <= 'f') {
171         return 10 + (c - 'a');
172       }
173     } else if (c >= 'A') {
174       if (c <= 'F') {
175         return 10 + (c - 'A');
176       }
177     } else if (c >= '0') {
178       if (c <= '9') {
179         return c - '0';
180       }
181     }
182     return -1;
183   }
184
185   protected static boolean isStringOfDigits(String value, int length) {
186     int stringLength = value.length();
187     if (length != stringLength) {
188       return false;
189     }
190     for (int i = 0; i < length; i++) {
191       char c = value.charAt(i);
192       if (c < '0' || c > '9') {
193         return false;
194       }
195     }
196     return true;
197   }
198
199   protected static Hashtable parseNameValuePairs(String uri) {
200     int paramStart = uri.indexOf('?');
201     if (paramStart < 0) {
202       return null;
203     }
204     Hashtable result = new Hashtable(3);
205     paramStart++;
206     int paramEnd;
207     while ((paramEnd = uri.indexOf('&', paramStart)) >= 0) {
208       appendKeyValue(uri, paramStart, paramEnd, result);
209       paramStart = paramEnd + 1;
210     }
211     appendKeyValue(uri, paramStart, uri.length(), result);
212     return result;
213   }
214
215   private static void appendKeyValue(String uri, int paramStart, int paramEnd, Hashtable result) {
216     int separator = uri.indexOf('=', paramStart);
217     if (separator >= 0) {
218       // key = value
219       String key = uri.substring(paramStart, separator);
220       String value = uri.substring(separator + 1, paramEnd);
221       value = urlDecode(value);
222       result.put(key, value);
223     } else {
224       // key, no value
225       String key = uri.substring(paramStart, paramEnd);
226       result.put(key, null);
227     }
228   }
229
230   static String[] matchPrefixedField(String prefix, String rawText, char endChar) {
231     Vector matches = null;
232     int i = 0;
233     int max = rawText.length();
234     while (i < max) {
235       i = rawText.indexOf(prefix, i);
236       if (i < 0) {
237         break;
238       }
239       i += prefix.length(); // Skip past this prefix we found to start
240       int start = i; // Found the start of a match here
241       boolean done = false;
242       while (!done) {
243         i = rawText.indexOf((int) endChar, i);
244         if (i < 0) {
245           // No terminating end character? uh, done. Set i such that loop terminates and break
246           i = rawText.length();
247           done = true;
248         } else if (rawText.charAt(i - 1) == '\\') {
249           // semicolon was escaped so continue
250           i++;
251         } else {
252           // found a match
253           if (matches == null) {
254             matches = new Vector(3); // lazy init
255           }
256           matches.addElement(unescapeBackslash(rawText.substring(start, i)));
257           i++;
258           done = true;
259         }
260       }
261     }
262     if (matches == null || matches.isEmpty()) {
263       return null;
264     }
265     return toStringArray(matches);
266   }
267
268   static String matchSinglePrefixedField(String prefix, String rawText, char endChar) {
269     String[] matches = matchPrefixedField(prefix, rawText, endChar);
270     return matches == null ? null : matches[0];
271   }
272
273   static String[] toStringArray(Vector strings) {
274     int size = strings.size();
275     String[] result = new String[size];
276     for (int j = 0; j < size; j++) {
277       result[j] = (String) strings.elementAt(j);
278     }
279     return result;
280   }
281
282 }