From 3f20333c84879b25cf70e145e543136ecf0f30bb Mon Sep 17 00:00:00 2001 From: srowen Date: Fri, 27 Jun 2008 17:50:47 +0000 Subject: [PATCH] More refactoring of parsed results / result parsers; added basic vCard support git-svn-id: http://zxing.googlecode.com/svn/trunk@487 59b500cc-1b3d-0410-9834-0bbf25fbcc57 --- .../android/BarcodeReaderCaptureActivity.java | 2 +- .../result/AbstractDoCoMoParsedResult.java | 35 ---- .../result/AbstractDoCoMoResultParser.java | 51 ------ .../result/AddressBookAUResultParser.java | 12 +- .../result/AddressBookParsedResult.java | 5 +- .../client/result/CalendarParsedResult.java | 2 +- .../result/EmailAddressParsedResult.java | 2 +- .../zxing/client/result/ResultParser.java | 73 +++++++- .../client/result/VCardResultParser.java | 161 ++++++++++++++++++ .../MobileTagSimpleContactResultParser.java | 3 + .../result/ParsedReaderResultTestCase.java | 4 +- .../google/zxing/client/j2me/ZXingMIDlet.java | 2 +- .../com/google/zxing/web/DecodeServlet.java | 2 +- 13 files changed, 250 insertions(+), 104 deletions(-) delete mode 100644 core/src/com/google/zxing/client/result/AbstractDoCoMoParsedResult.java create mode 100644 core/src/com/google/zxing/client/result/VCardResultParser.java diff --git a/android/src/com/google/zxing/client/android/BarcodeReaderCaptureActivity.java b/android/src/com/google/zxing/client/android/BarcodeReaderCaptureActivity.java index a8d67783..0962b2ca 100644 --- a/android/src/com/google/zxing/client/android/BarcodeReaderCaptureActivity.java +++ b/android/src/com/google/zxing/client/android/BarcodeReaderCaptureActivity.java @@ -229,7 +229,7 @@ public final class BarcodeReaderCaptureActivity extends Activity { } private static ParsedResult parseReaderResult(Result rawResult) { - ParsedResult result = ResultParser.parseReaderResult(rawResult); + ParsedResult result = ResultParser.parseResult(rawResult); if (result.getType().equals(ParsedResultType.TEXT)) { String rawText = rawResult.getText(); AndroidIntentParsedResult androidResult = AndroidIntentParsedResult.parse(rawText); diff --git a/core/src/com/google/zxing/client/result/AbstractDoCoMoParsedResult.java b/core/src/com/google/zxing/client/result/AbstractDoCoMoParsedResult.java deleted file mode 100644 index 8c802604..00000000 --- a/core/src/com/google/zxing/client/result/AbstractDoCoMoParsedResult.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2007 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; - -/** - *

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.

- * - * @author srowen@google.com (Sean Owen) - */ -abstract class AbstractDoCoMoParsedResult extends ParsedResult { - - AbstractDoCoMoParsedResult(ParsedResultType type) { - super(type); - } - -} \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/AbstractDoCoMoResultParser.java b/core/src/com/google/zxing/client/result/AbstractDoCoMoResultParser.java index 3b320e2f..b50cfdcb 100644 --- a/core/src/com/google/zxing/client/result/AbstractDoCoMoResultParser.java +++ b/core/src/com/google/zxing/client/result/AbstractDoCoMoResultParser.java @@ -16,8 +16,6 @@ package com.google.zxing.client.result; -import java.util.Vector; - /** *

See * @@ -37,57 +35,8 @@ abstract class AbstractDoCoMoResultParser extends ResultParser { return matchPrefixedField(prefix, rawText, ';'); } - 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; - } - int size = matches.size(); - String[] result = new String[size]; - for (int j = 0; j < size; j++) { - result[j] = (String) matches.elementAt(j); - } - return result; - } - static String matchSinglePrefixedField(String prefix, String rawText) { return matchSinglePrefixedField(prefix, rawText, ';'); } - static String matchSinglePrefixedField(String prefix, String rawText, char endChar) { - String[] matches = matchPrefixedField(prefix, rawText, endChar); - return matches == null ? null : matches[0]; - } - - } \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/AddressBookAUResultParser.java b/core/src/com/google/zxing/client/result/AddressBookAUResultParser.java index 32820986..4ed8967b 100644 --- a/core/src/com/google/zxing/client/result/AddressBookAUResultParser.java +++ b/core/src/com/google/zxing/client/result/AddressBookAUResultParser.java @@ -39,15 +39,15 @@ public final class AddressBookAUResultParser extends ResultParser { String[] names = matchMultipleValuePrefix("NAME", 2, rawText); String[] phoneNumbers = matchMultipleValuePrefix("TEL", 3, rawText); String[] emails = matchMultipleValuePrefix("MAIL", 3, rawText); - String note = AbstractDoCoMoResultParser.matchSinglePrefixedField("MEMORY:", rawText, '\r'); - String address = AbstractDoCoMoResultParser.matchSinglePrefixedField("ADD:", rawText, '\r'); + String note = matchSinglePrefixedField("MEMORY:", rawText, '\r'); + String address = matchSinglePrefixedField("ADD:", rawText, '\r'); return new AddressBookParsedResult(names, phoneNumbers, emails, note, address, null, null, null); } private static String[] matchMultipleValuePrefix(String prefix, int max, String rawText) { Vector values = null; for (int i = 1; i <= max; i++) { - String value = AbstractDoCoMoResultParser.matchSinglePrefixedField(prefix + i + ':', rawText, '\r'); + String value = matchSinglePrefixedField(prefix + i + ':', rawText, '\r'); if (value == null) { break; } @@ -59,11 +59,7 @@ public final class AddressBookAUResultParser extends ResultParser { if (values == null) { return null; } - String[] result = new String[values.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = (String) values.elementAt(i); - } - return result; + return toStringArray(values); } diff --git a/core/src/com/google/zxing/client/result/AddressBookParsedResult.java b/core/src/com/google/zxing/client/result/AddressBookParsedResult.java index cfefd293..f101b6b8 100644 --- a/core/src/com/google/zxing/client/result/AddressBookParsedResult.java +++ b/core/src/com/google/zxing/client/result/AddressBookParsedResult.java @@ -19,7 +19,7 @@ package com.google.zxing.client.result; /** * @author srowen@google.com (Sean Owen) */ -public final class AddressBookParsedResult extends AbstractDoCoMoParsedResult { +public final class AddressBookParsedResult extends ParsedResult { private final String[] names; private final String[] phoneNumbers; @@ -77,6 +77,9 @@ public final class AddressBookParsedResult extends AbstractDoCoMoParsedResult { return org; } + /** + * @return birthday formatted as yyyyMMdd (e.g. 19780917) + */ public String getBirthday() { return birthday; } diff --git a/core/src/com/google/zxing/client/result/CalendarParsedResult.java b/core/src/com/google/zxing/client/result/CalendarParsedResult.java index c6f54646..687e36b0 100644 --- a/core/src/com/google/zxing/client/result/CalendarParsedResult.java +++ b/core/src/com/google/zxing/client/result/CalendarParsedResult.java @@ -52,7 +52,7 @@ public final class CalendarParsedResult extends ParsedResult { * needs to work under JavaME / MIDP and there is no date parsing library available there, such * as java.text.SimpleDateFormat.

* - *

Instead this is a String formatted as YYYYMMdd'T'HHmmss'Z'.

+ * @return start time formatted as yyyyMMdd'T'HHmmss'Z'.

*/ public String getStart() { return start; diff --git a/core/src/com/google/zxing/client/result/EmailAddressParsedResult.java b/core/src/com/google/zxing/client/result/EmailAddressParsedResult.java index 664a8bc0..a0542da6 100644 --- a/core/src/com/google/zxing/client/result/EmailAddressParsedResult.java +++ b/core/src/com/google/zxing/client/result/EmailAddressParsedResult.java @@ -19,7 +19,7 @@ package com.google.zxing.client.result; /** * @author srowen@google.com (Sean Owen) */ -public final class EmailAddressParsedResult extends AbstractDoCoMoParsedResult { +public final class EmailAddressParsedResult extends ParsedResult { private final String emailAddress; private final String subject; diff --git a/core/src/com/google/zxing/client/result/ResultParser.java b/core/src/com/google/zxing/client/result/ResultParser.java index 440c6e5b..2f88fff4 100644 --- a/core/src/com/google/zxing/client/result/ResultParser.java +++ b/core/src/com/google/zxing/client/result/ResultParser.java @@ -19,11 +19,12 @@ package com.google.zxing.client.result; import com.google.zxing.Result; import java.util.Hashtable; +import java.util.Vector; /** *

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 00000000..7d5677bc --- /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 412f878e..261ebb46 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 af10e0ef..07660156 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 b1aa9be4..00c4073b 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 e60536c3..b2428a50 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); } -- 2.20.1