From aa65741586fe6c0b1035c088e6400a119d8fa8ef Mon Sep 17 00:00:00 2001
From: srowen See
- *
- * DoCoMo's documentation about the result types represented by subclasses of this class. Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
- * on exception-based mechanisms during parsing. Instead this is a String formatted as YYYYMMdd'T'HHmmss'Z'.
Abstract class representing the result of decoding a barcode, as more than * a String -- as some type of structured data. This might be a subclass which represents - * a URL, or an e-mail address. {@link #parseReaderResult(com.google.zxing.Result)} will turn a raw + * a URL, or an e-mail address. {@link #parseResult(com.google.zxing.Result)} will turn a raw * decoded string into the most appropriate type of structured representation.
* *Thanks to Jeff Griffin for proposing rewrite of these classes that relies less @@ -33,7 +34,7 @@ import java.util.Hashtable; */ public abstract class ResultParser { - public static ParsedResult parseReaderResult(Result theResult) { + public static ParsedResult parseResult(Result theResult) { // This is a bit messy, but given limited options in MIDP / CLDC, this may well be the simplest // way to go about this. For example, we have no reflection available, really. // Order is important here. @@ -48,6 +49,8 @@ public abstract class ResultParser { return result; } else if ((result = AddressBookAUResultParser.parse(theResult)) != null) { return result; + } else if ((result = VCardResultParser.parse(theResult)) != null) { + return result; } else if ((result = TelResultParser.parse(theResult)) != null) { return result; } else if ((result = SMSResultParser.parse(theResult)) != null) { @@ -179,6 +182,20 @@ public abstract class ResultParser { return -1; } + protected static boolean isStringOfDigits(String value, int length) { + int stringLength = value.length(); + if (length != stringLength) { + return false; + } + for (int i = 0; i < length; i++) { + char c = value.charAt(i); + if (c < '0' || c > '9') { + return false; + } + } + return true; + } + protected static Hashtable parseNameValuePairs(String uri) { int paramStart = uri.indexOf('?'); if (paramStart < 0) { @@ -210,4 +227,56 @@ public abstract class ResultParser { } } + static String[] matchPrefixedField(String prefix, String rawText, char endChar) { + Vector matches = null; + int i = 0; + int max = rawText.length(); + while (i < max) { + i = rawText.indexOf(prefix, i); + if (i < 0) { + break; + } + i += prefix.length(); // Skip past this prefix we found to start + int start = i; // Found the start of a match here + boolean done = false; + while (!done) { + i = rawText.indexOf((int) endChar, i); + if (i < 0) { + // No terminating end character? uh, done. Set i such that loop terminates and break + i = rawText.length(); + done = true; + } else if (rawText.charAt(i - 1) == '\\') { + // semicolon was escaped so continue + i++; + } else { + // found a match + if (matches == null) { + matches = new Vector(3); // lazy init + } + matches.addElement(unescapeBackslash(rawText.substring(start, i))); + i++; + done = true; + } + } + } + if (matches == null || matches.isEmpty()) { + return null; + } + return toStringArray(matches); + } + + static String matchSinglePrefixedField(String prefix, String rawText, char endChar) { + String[] matches = matchPrefixedField(prefix, rawText, endChar); + return matches == null ? null : matches[0]; + } + + static String[] toStringArray(Vector strings) { + int size = strings.size(); + String[] result = new String[size]; + for (int j = 0; j < size; j++) { + result[j] = (String) strings.elementAt(j); + } + return result; + } + } \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/VCardResultParser.java b/core/src/com/google/zxing/client/result/VCardResultParser.java new file mode 100644 index 000000000..7d5677bc7 --- /dev/null +++ b/core/src/com/google/zxing/client/result/VCardResultParser.java @@ -0,0 +1,161 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.client.result; + +import com.google.zxing.Result; + +import java.util.Vector; + +/** + * Parses contact information formatted according to the VCard (2.1) format. This is not a complete + * implementation but should parse information as commonly encoded in 2D barcodes. + * + * @author srowen@google.com (Sean Owen) + */ +public final class VCardResultParser extends ResultParser { + + private VCardResultParser() { + } + + public static AddressBookParsedResult parse(Result result) { + String rawText = result.getText(); + if (rawText == null || !rawText.startsWith("BEGIN:VCARD") || !rawText.endsWith("END:VCARD")) { + return null; + } + String[] names = matchVCardPrefixedField("FN", rawText); + if (names == null) { + // If no display names found, look for regular name fields and format them + names = matchVCardPrefixedField("N", rawText); + formatNames(names); + } + String[] phoneNumbers = matchVCardPrefixedField("TEL", rawText); + String[] emails = matchVCardPrefixedField("EMAIL", rawText); + String note = matchSingleVCardPrefixedField("NOTE", rawText); + String address = matchSingleVCardPrefixedField("ADR", rawText); + address = formatAddress(address); + String org = matchSingleVCardPrefixedField("ORG", rawText); + String birthday = matchSingleVCardPrefixedField("BDAY", rawText); + if (!isStringOfDigits(birthday, 8)) { + return null; + } + String title = matchSingleVCardPrefixedField("TITLE", rawText); + return new AddressBookParsedResult(names, phoneNumbers, emails, note, address, org, birthday, title); + } + + private static String[] matchVCardPrefixedField(String prefix, String rawText) { + Vector matches = null; + int i = 0; + int max = rawText.length(); + while (i < max) { + i = rawText.indexOf(prefix, i); + if (i < 0) { + break; + } + if (rawText.charAt(i - 1) != '\n') { + // then this didn't start a new token, we matched in the middle of something + i++; + continue; + } + i += prefix.length(); // Skip past this prefix we found to start + if (rawText.charAt(i) != ':' && rawText.charAt(i) != ';') { + continue; + } + while (rawText.charAt(i) != ':') { // Skip until a colon + i++; + } + i++; // skip colon + int start = i; // Found the start of a match here + boolean done = false; + while (!done) { + i = rawText.indexOf((int) '\n', i); // Really, ends in \r\n + if (i < 0) { + // No terminating end character? uh, done. Set i such that loop terminates and break + i = rawText.length(); + done = true; + } else { + // found a match + if (matches == null) { + matches = new Vector(3); // lazy init + } + matches.addElement(rawText.substring(start, i - 1)); // i - 1 to strip off the \r too + i++; + done = true; + } + } + } + if (matches == null || matches.isEmpty()) { + return null; + } + return toStringArray(matches); + } + + private static String matchSingleVCardPrefixedField(String prefix, String rawText) { + String[] values = matchVCardPrefixedField(prefix, rawText); + return values == null ? null : values[0]; + } + + private static String formatAddress(String address) { + int length = address.length(); + StringBuffer newAddress = new StringBuffer(length); + for (int j = 0; j < length; j++) { + char c = address.charAt(j); + if (c == ';') { + newAddress.append(' '); + } else { + newAddress.append(c); + } + } + return newAddress.toString().trim(); + } + + /** + * Formats name fields of the form "Public;John;Q.;Reverend;III" into a form like + * "Reverend John Q. Public III". + * + * @param names name values to format, in place + */ + private static void formatNames(String[] names) { + for (int i = 0; i < names.length; i++) { + String name = names[i]; + String[] components = new String[5]; + int start = 0; + int end; + int componentIndex = 0; + while ((end = name.indexOf(';', start)) > 0) { + components[componentIndex] = name.substring(start, end); + componentIndex++; + start = end + 1; + } + components[componentIndex] = name.substring(start); + StringBuffer newName = new StringBuffer(); + maybeAppendComponent(components, 3, newName); + maybeAppendComponent(components, 1, newName); + maybeAppendComponent(components, 2, newName); + maybeAppendComponent(components, 0, newName); + maybeAppendComponent(components, 4, newName); + names[i] = newName.toString().trim(); + } + } + + private static void maybeAppendComponent(String[] components, int i, StringBuffer newName) { + if (components[i] != null) { + newName.append(' '); + newName.append(components[i]); + } + } + +} \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/optional/MobileTagSimpleContactResultParser.java b/core/src/com/google/zxing/client/result/optional/MobileTagSimpleContactResultParser.java index 412f878ea..261ebb46d 100644 --- a/core/src/com/google/zxing/client/result/optional/MobileTagSimpleContactResultParser.java +++ b/core/src/com/google/zxing/client/result/optional/MobileTagSimpleContactResultParser.java @@ -51,6 +51,9 @@ public final class MobileTagSimpleContactResultParser extends AbstractMobileTagR String address = matches[5]; String org = matches[6]; String birthday = matches[7]; + if (!isStringOfDigits(birthday, 8)) { + return null; + } String title = matches[8]; return new AddressBookParsedResult(new String[] {fullName}, diff --git a/core/test/src/com/google/zxing/client/result/ParsedReaderResultTestCase.java b/core/test/src/com/google/zxing/client/result/ParsedReaderResultTestCase.java index af10e0efe..076601563 100644 --- a/core/test/src/com/google/zxing/client/result/ParsedReaderResultTestCase.java +++ b/core/test/src/com/google/zxing/client/result/ParsedReaderResultTestCase.java @@ -145,14 +145,14 @@ public final class ParsedReaderResultTestCase extends TestCase { private static void doTestResult(String text, ParsedResultType type, BarcodeFormat format) { Result fakeResult = new Result(text, null, null, format); - ParsedResult result = ResultParser.parseReaderResult(fakeResult); + ParsedResult result = ResultParser.parseResult(fakeResult); assertNotNull(result); assertEquals(type, result.getType()); } private static void doTestResult(byte[] rawBytes, ParsedResultType type) { Result fakeResult = new Result(null, rawBytes, null, null); - ParsedResult result = ResultParser.parseReaderResult(fakeResult); + ParsedResult result = ResultParser.parseResult(fakeResult); assertNotNull(result); assertEquals(type, result.getType()); } diff --git a/javame/src/com/google/zxing/client/j2me/ZXingMIDlet.java b/javame/src/com/google/zxing/client/j2me/ZXingMIDlet.java index b1aa9be41..00c4073bf 100644 --- a/javame/src/com/google/zxing/client/j2me/ZXingMIDlet.java +++ b/javame/src/com/google/zxing/client/j2me/ZXingMIDlet.java @@ -192,7 +192,7 @@ public final class ZXingMIDlet extends MIDlet { } void handleDecodedText(Result theResult) { - ParsedResult result = ResultParser.parseReaderResult(theResult); + ParsedResult result = ResultParser.parseResult(theResult); ParsedResultType type = result.getType(); if (type.equals(ParsedResultType.URI)) { String uri = ((URIParsedResult) result).getURI(); diff --git a/zxingorg/src/com/google/zxing/web/DecodeServlet.java b/zxingorg/src/com/google/zxing/web/DecodeServlet.java index e60536c31..b2428a502 100644 --- a/zxingorg/src/com/google/zxing/web/DecodeServlet.java +++ b/zxingorg/src/com/google/zxing/web/DecodeServlet.java @@ -244,7 +244,7 @@ public final class DecodeServlet extends HttpServlet { } else { request.setAttribute("rawBytesString", "(Not applicable)"); } - ParsedResult parsedResult = ResultParser.parseReaderResult(result); + ParsedResult parsedResult = ResultParser.parseResult(result); request.setAttribute("parsedResult", parsedResult); request.getRequestDispatcher("decoderesult.jspx").forward(request, response); }