More refactoring of parsed results / result parsers; added basic vCard support

git-svn-id: https://zxing.googlecode.com/svn/trunk@487 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen 2008-06-27 17:50:47 +00:00
parent dd57042f9a
commit aa65741586
13 changed files with 250 additions and 104 deletions

View file

@ -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);

View file

@ -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;
/**
* <p>See
* <a href="http://www.nttdocomo.co.jp/english/service/imode/make/content/barcode/about/s2.html">
* DoCoMo's documentation</a> about the result types represented by subclasses of this class.</p>
*
* <p>Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
* on exception-based mechanisms during parsing.</p>
*
* @author srowen@google.com (Sean Owen)
*/
abstract class AbstractDoCoMoParsedResult extends ParsedResult {
AbstractDoCoMoParsedResult(ParsedResultType type) {
super(type);
}
}

View file

@ -16,8 +16,6 @@
package com.google.zxing.client.result;
import java.util.Vector;
/**
* <p>See
* <a href="http://www.nttdocomo.co.jp/english/service/imode/make/content/barcode/about/s2.html">
@ -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];
}
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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 <code>java.text.SimpleDateFormat</code>.</p>
*
* <p>Instead this is a String formatted as YYYYMMdd'T'HHmmss'Z'.</p>
* @return start time formatted as yyyyMMdd'T'HHmmss'Z'.</p>
*/
public String getStart() {
return start;

View file

@ -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;

View file

@ -19,11 +19,12 @@ package com.google.zxing.client.result;
import com.google.zxing.Result;
import java.util.Hashtable;
import java.util.Vector;
/**
* <p>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.</p>
*
* <p>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;
}
}

View file

@ -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]);
}
}
}

View file

@ -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},

View file

@ -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());
}

View file

@ -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();

View file

@ -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);
}