2 * Copyright 2007 ZXing authors
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.google.zxing.client.result;
19 import com.google.zxing.Result;
21 import java.util.Hashtable;
22 import java.util.Vector;
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>
30 * <p>Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
31 * on exception-based mechanisms during parsing.</p>
35 public abstract class ResultParser {
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.
42 if ((result = BookmarkDoCoMoResultParser.parse(theResult)) != null) {
44 } else if ((result = AddressBookDoCoMoResultParser.parse(theResult)) != null) {
46 } else if ((result = EmailDoCoMoResultParser.parse(theResult)) != null) {
48 } else if ((result = AddressBookAUResultParser.parse(theResult)) != null) {
50 } else if ((result = VCardResultParser.parse(theResult)) != null) {
52 } else if ((result = BizcardResultParser.parse(theResult)) != null) {
54 } else if ((result = VEventResultParser.parse(theResult)) != null) {
56 } else if ((result = EmailAddressResultParser.parse(theResult)) != null) {
58 } else if ((result = SMTPResultParser.parse(theResult)) != null) {
60 } else if ((result = TelResultParser.parse(theResult)) != null) {
62 } else if ((result = SMSMMSResultParser.parse(theResult)) != null) {
64 } else if ((result = SMSTOMMSTOResultParser.parse(theResult)) != null) {
66 } else if ((result = GeoResultParser.parse(theResult)) != null) {
68 } else if ((result = WifiResultParser.parse(theResult)) != null) {
70 } else if ((result = URLTOResultParser.parse(theResult)) != null) {
72 } else if ((result = URIResultParser.parse(theResult)) != null) {
74 } else if ((result = ISBNResultParser.parse(theResult)) != null) {
75 // We depend on ISBN parsing coming before UPC, as it is a subset.
77 } else if ((result = ProductResultParser.parse(theResult)) != null) {
79 } else if ((result = ExpandedProductResultParser.parse(theResult)) != null) {
82 return new TextParsedResult(theResult.getText(), null);
85 protected static void maybeAppend(String value, StringBuffer result) {
92 protected static void maybeAppend(String[] value, StringBuffer result) {
94 for (int i = 0; i < value.length; i++) {
96 result.append(value[i]);
101 protected static String[] maybeWrap(String value) {
102 return value == null ? null : new String[] { value };
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 != '\\') {
117 nextIsEscaped = false;
119 nextIsEscaped = true;
122 return unescaped.toString();
128 private static String urlDecode(String escaped) {
130 // No we can't use java.net.URLDecoder here. JavaME doesn't have it.
131 if (escaped == null) {
134 char[] escapedArray = escaped.toCharArray();
136 int first = findFirstEscape(escapedArray);
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);
147 for (int i = first; i < max; i++) {
148 char c = escapedArray[i];
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
155 unescaped.append('%'); // append that % and move on
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]);
165 unescaped.append((char) ((firstDigitValue << 4) + secondDigitValue));
171 return unescaped.toString();
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 == '%') {
185 private static int parseHexDigit(char c) {
188 return 10 + (c - 'a');
190 } else if (c >= 'A') {
192 return 10 + (c - 'A');
194 } else if (c >= '0') {
202 protected static boolean isStringOfDigits(String value, int length) {
206 int stringLength = value.length();
207 if (length != stringLength) {
210 for (int i = 0; i < length; i++) {
211 char c = value.charAt(i);
212 if (c < '0' || c > '9') {
219 protected static boolean isSubstringOfDigits(String value, int offset, int length) {
223 int stringLength = value.length();
224 int max = offset + length;
225 if (stringLength < max) {
228 for (int i = offset; i < max; i++) {
229 char c = value.charAt(i);
230 if (c < '0' || c > '9') {
237 static Hashtable parseNameValuePairs(String uri) {
238 int paramStart = uri.indexOf('?');
239 if (paramStart < 0) {
242 Hashtable result = new Hashtable(3);
245 while ((paramEnd = uri.indexOf('&', paramStart)) >= 0) {
246 appendKeyValue(uri, paramStart, paramEnd, result);
247 paramStart = paramEnd + 1;
249 appendKeyValue(uri, paramStart, uri.length(), result);
253 private static void appendKeyValue(String uri, int paramStart, int paramEnd, Hashtable result) {
254 int separator = uri.indexOf('=', paramStart);
255 if (separator >= 0) {
257 String key = uri.substring(paramStart, separator);
258 String value = uri.substring(separator + 1, paramEnd);
259 value = urlDecode(value);
260 result.put(key, value);
262 // Can't put key, null into a hashtable
265 static String[] matchPrefixedField(String prefix, String rawText, char endChar, boolean trim) {
266 Vector matches = null;
268 int max = rawText.length();
270 i = rawText.indexOf(prefix, i);
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;
278 i = rawText.indexOf((int) endChar, i);
280 // No terminating end character? uh, done. Set i such that loop terminates and break
281 i = rawText.length();
283 } else if (rawText.charAt(i - 1) == '\\') {
284 // semicolon was escaped so continue
288 if (matches == null) {
289 matches = new Vector(3); // lazy init
291 String element = unescapeBackslash(rawText.substring(start, i));
293 element = element.trim();
295 matches.addElement(element);
301 if (matches == null || matches.isEmpty()) {
304 return toStringArray(matches);
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];
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);