Add comments for vCard parser.

Bug: 2576594
Change-Id: I3303a350bdea7b1b84bd1fb318d31ba5a229457d
This commit is contained in:
Daisuke Miyakawa
2010-04-09 14:49:04 +09:00
parent 182a2f380f
commit 4ac671ad97
6 changed files with 144 additions and 99 deletions

View File

@@ -91,10 +91,12 @@ public class VCardBuilder {
private static final String VCARD_WS = " ";
private static final String VCARD_PARAM_EQUAL = "=";
private static final String VCARD_PARAM_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
private static final String VCARD_PARAM_ENCODING_BASE64_V21 = "ENCODING=BASE64";
private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b";
private static final String VCARD_PARAM_ENCODING_QP =
"ENCODING=" + VCardConstants.PARAM_ENCODING_QP;
private static final String VCARD_PARAM_ENCODING_BASE64_V21 =
"ENCODING=" + VCardConstants.PARAM_ENCODING_BASE64;
private static final String VCARD_PARAM_ENCODING_BASE64_V30 =
"ENCODING=" + VCardConstants.PARAM_ENCODING_B;
private static final String SHIFT_JIS = "SHIFT_JIS";

View File

@@ -109,6 +109,12 @@ public class VCardConstants {
public static final String PARAM_TYPE_BBS = "BBS";
public static final String PARAM_TYPE_VIDEO = "VIDEO";
public static final String PARAM_ENCODING_7BIT = "7BIT";
public static final String PARAM_ENCODING_8BIT = "8BIT";
public static final String PARAM_ENCODING_QP = "QUOTED-PRINTABLE";
public static final String PARAM_ENCODING_BASE64 = "BASE64"; // Available in vCard 2.1
public static final String PARAM_ENCODING_B = "B"; // Available in vCard 3.0
// TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1).
// These types are basically encoded to "X-" parameters when composing vCard.
// Parser passes these when "X-" is added to the parameter or not.
@@ -130,10 +136,6 @@ public class VCardConstants {
// Do not use in composer side.
public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY";
// DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of SORT-STRING in
// vCard 3.0.
public static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
public interface ImportOnly {
public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
// Some device emits this "X-" parameter for expressing Google Talk,
@@ -142,6 +144,12 @@ public class VCardConstants {
public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
}
//// Mainly for package constants.
// DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of
// SORT-STRING invCard 3.0.
/* package */ static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
/* package */ static final int MAX_DATA_COLUMN = 15;
/* package */ static final int MAX_CHARACTER_NUMS_QP = 76;

View File

@@ -63,10 +63,7 @@ import java.util.Set;
private static final String sDefaultEncoding = "8BIT";
//// Protected members
protected boolean mCanceled;
protected VCardInterpreter mInterpreter;
/**
@@ -109,7 +106,6 @@ import java.util.Set;
*/
protected final Set<String> mUnknownValueSet = new HashSet<String>();
// // Private members
// In some cases, vCard is nested. Currently, we only consider the most
// interior vCard data.
@@ -121,27 +117,16 @@ import java.util.Set;
// For measuring performance.
private long mTimeTotal;
private long mTimeReadStartRecord;
private long mTimeReadEndRecord;
private long mTimeStartProperty;
private long mTimeEndProperty;
private long mTimeParseItems;
private long mTimeParseLineAndHandleGroup;
private long mTimeParsePropertyValues;
private long mTimeParseAdrOrgN;
private long mTimeHandleMiscPropertyValue;
private long mTimeHandleQuotedPrintable;
private long mTimeHandleBase64;
/**
@@ -156,8 +141,7 @@ import java.util.Set;
/**
* <p>
* The constructor which uses the estimated type available from a given
* detector.
* The constructor which uses the estimated type available from a given detector.
* </p>
*/
public VCardParserImpl_V21(VCardSourceDetector detector) {
@@ -186,15 +170,15 @@ import java.util.Set;
*/
// <pre class="prettyprint">vcard_file = [wsls] vcard [wsls]</pre>
protected void parseVCardFile() throws IOException, VCardException {
boolean firstReading = true;
boolean firstRead = true;
while (true) {
if (mCanceled) {
break;
}
if (!parseOneVCard(firstReading)) {
if (!parseOneVCard(firstRead)) {
break;
}
firstReading = false;
firstRead = false;
}
if (mNestCount > 0) {
@@ -245,12 +229,13 @@ import java.util.Set;
}
/*
* vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws]
* ":" [ws] "VCARD"
* vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
* items *CRLF
* "END" [ws] ":" [ws] "VCARD"
*/
private boolean parseOneVCard(boolean firstReading) throws IOException, VCardException {
private boolean parseOneVCard(boolean firstRead) throws IOException, VCardException {
boolean allowGarbage = false;
if (firstReading) {
if (firstRead) {
if (mNestCount > 0) {
for (int i = 0; i < mNestCount; i++) {
if (!readBeginVCard(allowGarbage)) {
@@ -456,34 +441,29 @@ import java.util.Set;
throw new VCardException("Unknown property name: \"" + propertyName + "\"");
}
static private final int STATE_GROUP_OR_PROPNAME = 0;
// For performance reason, the states for group and property name are merged into one.
static private final int STATE_GROUP_OR_PROPERTY_NAME = 0;
static private final int STATE_PARAMS = 1;
// vCard 3.0 specification allows double-quoted param-value, while vCard 2.1
// does not.
// This is just for safety.
// vCard 3.0 specification allows double-quoted parameters, while vCard 2.1 does not.
static private final int STATE_PARAMS_IN_DQUOTE = 2;
protected String[] separateLineAndHandleGroup(String line) throws VCardException {
int state = STATE_GROUP_OR_PROPNAME;
int nameIndex = 0;
final String[] propertyNameAndValue = new String[2];
final int length = line.length();
if (length > 0 && line.charAt(0) == '#') {
throw new VCardInvalidCommentLineException();
}
// This loop is developed so that we don't have to take care of bottle
// neck here.
int state = STATE_GROUP_OR_PROPERTY_NAME;
int nameIndex = 0;
// This loop is developed so that we don't have to take care of bottle neck here.
// Refactor carefully when you need to do so.
for (int i = 0; i < length; i++) {
final char ch = line.charAt(i);
switch (state) {
case STATE_GROUP_OR_PROPNAME: {
if (ch == ':') {
case STATE_GROUP_OR_PROPERTY_NAME: {
if (ch == ':') { // End of a property name.
final String propertyName = line.substring(nameIndex, i);
if (propertyName.equalsIgnoreCase("END")) {
mPreviousLine = line;
@@ -499,14 +479,16 @@ import java.util.Set;
propertyNameAndValue[1] = "";
}
return propertyNameAndValue;
} else if (ch == '.') {
String groupName = line.substring(nameIndex, i);
if (mInterpreter != null) {
} else if (ch == '.') { // Each group is followed by the dot.
final String groupName = line.substring(nameIndex, i);
if (groupName.length() == 0) {
Log.w(LOG_TAG, "Empty group found. Ignoring.");
} else if (mInterpreter != null) {
mInterpreter.propertyGroup(groupName);
}
nameIndex = i + 1;
} else if (ch == ';') {
String propertyName = line.substring(nameIndex, i);
nameIndex = i + 1; // Next should be another group or a property name.
} else if (ch == ';') { // End of property name and beginneng of parameters.
final String propertyName = line.substring(nameIndex, i);
if (propertyName.equalsIgnoreCase("END")) {
mPreviousLine = line;
return null;
@@ -516,17 +498,21 @@ import java.util.Set;
}
propertyNameAndValue[0] = propertyName;
nameIndex = i + 1;
state = STATE_PARAMS;
state = STATE_PARAMS; // Start parameter parsing.
}
break;
}
case STATE_PARAMS: {
if (ch == '"') {
if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
"Silently allow it");
}
state = STATE_PARAMS_IN_DQUOTE;
} else if (ch == ';') {
} else if (ch == ';') { // Starts another param.
handleParams(line.substring(nameIndex, i));
nameIndex = i + 1;
} else if (ch == ':') {
} else if (ch == ':') { // End of param and beginenning of values.
handleParams(line.substring(nameIndex, i));
if (i < length - 1) {
propertyNameAndValue[1] = line.substring(i + 1);
@@ -539,6 +525,10 @@ import java.util.Set;
}
case STATE_PARAMS_IN_DQUOTE: {
if (ch == '"') {
if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
"Silently allow it");
}
state = STATE_PARAMS;
}
break;
@@ -557,7 +547,7 @@ import java.util.Set;
* [ws] word / knowntype
*/
protected void handleParams(String params) throws VCardException {
String[] strArray = params.split("=", 2);
final String[] strArray = params.split("=", 2);
if (strArray.length == 2) {
final String paramName = strArray[0].trim().toUpperCase();
String paramValue = strArray[1].trim();
@@ -582,7 +572,7 @@ import java.util.Set;
}
/**
* vCard 3.0 parser may throw VCardException.
* vCard 3.0 parser implementation may throw VCardException.
*/
@SuppressWarnings("unused")
protected void handleParamWithoutName(final String paramValue) throws VCardException {
@@ -593,15 +583,15 @@ import java.util.Set;
* ptypeval = knowntype / "X-" word
*/
protected void handleType(final String ptypeval) {
String upperTypeValue = ptypeval;
if (!(getKnownTypeSet().contains(upperTypeValue) || upperTypeValue.startsWith("X-"))
if (!(getKnownTypeSet().contains(ptypeval.toUpperCase())
|| ptypeval.startsWith("X-"))
&& !mUnknownTypeSet.contains(ptypeval)) {
mUnknownTypeSet.add(ptypeval);
Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), ptypeval));
}
if (mInterpreter != null) {
mInterpreter.propertyParamType("TYPE");
mInterpreter.propertyParamValue(upperTypeValue);
mInterpreter.propertyParamValue(ptypeval);
}
}
@@ -609,10 +599,12 @@ import java.util.Set;
* pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
*/
protected void handleValue(final String pvalueval) {
if (!getKnownValueSet().contains(pvalueval.toUpperCase()) && pvalueval.startsWith("X-")
&& !mUnknownValueSet.contains(pvalueval)) {
if (!(getKnownValueSet().contains(pvalueval.toUpperCase())
|| pvalueval.startsWith("X-")
|| mUnknownValueSet.contains(pvalueval))) {
mUnknownValueSet.add(pvalueval);
Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), pvalueval));
Log.w(LOG_TAG, String.format(
"The value unsupported by TYPE of %s: ", getVersion(), pvalueval));
}
if (mInterpreter != null) {
mInterpreter.propertyParamType("VALUE");
@@ -621,8 +613,7 @@ import java.util.Set;
}
/*
* pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-"
* word
* pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
*/
protected void handleEncoding(String pencodingval) throws VCardException {
if (getAvailableEncodingSet().contains(pencodingval) ||
@@ -638,8 +629,11 @@ import java.util.Set;
}
/**
* vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC
* 1521), but today's vCard often contains other charset, so we allow them.
* <p>
* vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
* but recent vCard files often contain other charset like UTF-8, SHIFT_JIS, etc.
* We allow any charset.
* </p>
*/
protected void handleCharset(String charsetval) {
if (mInterpreter != null) {
@@ -695,7 +689,8 @@ import java.util.Set;
protected void handlePropertyValue(String propertyName, String propertyValue)
throws IOException, VCardException {
if (mCurrentEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
final String upperEncoding = mCurrentEncoding.toUpperCase();
if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_QP)) {
final long start = System.currentTimeMillis();
final String result = getQuotedPrintable(propertyValue);
if (mInterpreter != null) {
@@ -704,8 +699,8 @@ import java.util.Set;
mInterpreter.propertyValues(v);
}
mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
} else if (mCurrentEncoding.equalsIgnoreCase("BASE64")
|| mCurrentEncoding.equalsIgnoreCase("B")) {
} else if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_BASE64)
|| upperEncoding.equals(VCardConstants.PARAM_ENCODING_B)) {
final long start = System.currentTimeMillis();
// It is very rare, but some BASE64 data may be so big that
// OutOfMemoryError occurs. To ignore such cases, use try-catch.
@@ -724,11 +719,11 @@ import java.util.Set;
}
mTimeHandleBase64 += System.currentTimeMillis() - start;
} else {
if (!(mCurrentEncoding == null || mCurrentEncoding.equalsIgnoreCase("7BIT")
|| mCurrentEncoding.equalsIgnoreCase("8BIT") || mCurrentEncoding.toUpperCase()
.startsWith("X-"))) {
Log.w(LOG_TAG, "The encoding unsupported by vCard spec: \"" + mCurrentEncoding
+ "\".");
if (!(upperEncoding.equals("7BIT") || upperEncoding.equals("8BIT") ||
upperEncoding.startsWith("X-"))) {
Log.w(LOG_TAG,
String.format("The encoding \"%s\" is unsupported by vCard %s",
mCurrentEncoding, getVersionString()));
}
final long start = System.currentTimeMillis();

View File

@@ -21,11 +21,22 @@ import java.util.Set;
import android.pim.vcard.exception.VCardException;
import android.util.Log;
/**
* <p>
* Basic implementation achieving vCard 3.0 parsing.
* </p>
* <p>
* This class inherits vCard 2.1 implementation since technically they are similar,
* while specifically there's logical no relevance between them.
* So that developers are not confused with the inheritance,
* {@link VCardParser_V30} does not inherit {@link VCardParser_V21}, while
* {@link VCardParserImpl_V30} inherits {@link VCardParserImpl_V21}.
* </p>
*/
/* package */ class VCardParserImpl_V30 extends VCardParserImpl_V21 {
private static final String LOG_TAG = "VCardParserImpl_V30";
private String mPreviousLine;
private boolean mEmittedAgentWarning = false;
public VCardParserImpl_V30() {
@@ -239,8 +250,7 @@ import android.util.Log;
while (true) {
final String line = getLine();
if (line == null) {
throw new VCardException(
"File ended during parsing BASE64 binary");
throw new VCardException("File ended during parsing BASE64 binary");
}
if (line.length() == 0) {
break;

View File

@@ -26,14 +26,13 @@ import java.util.Set;
/**
* </p>
* vCard parser implementation mostly for vCard 2.1. See the specification for more detail
* about the spec itself.
* vCard parser for vCard 2.1. See the specification for more detail about the spec itself.
* </p>
* <p>
* The spec is written in 1996, and currently various types of "vCard 2.1" exist.
* To handle real the world vCard formats appropriately and effectively, this class does not
* obey with strict vCard 2.1. In stead, not only vCard spec but also real world
* vCard is considered.
* obey with strict vCard 2.1.
* In stead, not only vCard spec but also real world vCard is considered.
* </p>
* e.g. A lot of devices and softwares let vCard importer/exporter to use
* the PNG format to determine the type of image, while it is not allowed in
@@ -45,7 +44,7 @@ public final class VCardParser_V21 implements VCardParser {
/**
* A unmodifiable Set storing the property names available in the vCard 2.1 specification.
*/
public static final Set<String> sKnownPropertyNameSet =
/* package */ static final Set<String> sKnownPropertyNameSet =
Collections.unmodifiableSet(new HashSet<String>(
Arrays.asList("BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
@@ -54,7 +53,7 @@ public final class VCardParser_V21 implements VCardParser {
/**
* A unmodifiable Set storing the types known in vCard 2.1.
*/
public static final Set<String> sKnownTypeSet =
/* package */ static final Set<String> sKnownTypeSet =
Collections.unmodifiableSet(new HashSet<String>(
Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
"PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
@@ -66,20 +65,28 @@ public final class VCardParser_V21 implements VCardParser {
"WAVE", "AIFF", "PCM", "X509", "PGP")));
/**
* A unmodifiable Set storing the values available in the vCard 2.1 specification.
* A unmodifiable Set storing the values for the type "VALUE", available in the vCard 2.1.
*/
public static final Set<String> sKnownValueSet =
/* package */ static final Set<String> sKnownValueSet =
Collections.unmodifiableSet(new HashSet<String>(
Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID")));
/**
* <p>
* A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 2.1.
* </p>
* <p>
* Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
* We allow it for safety...
* We allow it for safety.
* </p>
*/
// TODO: move B to another something and make this member public
/* package */ static final HashSet<String> sAvailableEncoding =
new HashSet<String>(Arrays.asList(
"7BIT", "8BIT", "QUOTED-PRINTABLE", "BASE64", "B"));
/* package */ static final Set<String> sAvailableEncoding =
Collections.unmodifiableSet(new HashSet<String>(
Arrays.asList(VCardConstants.PARAM_ENCODING_7BIT,
VCardConstants.PARAM_ENCODING_8BIT,
VCardConstants.PARAM_ENCODING_QP,
VCardConstants.PARAM_ENCODING_BASE64,
VCardConstants.PARAM_ENCODING_B)));
private final VCardParserImpl_V21 mVCardParserImpl;

View File

@@ -25,22 +25,46 @@ import java.util.HashSet;
import java.util.Set;
/**
* The class used to parse vCard 3.0.
* Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426).
* <p>
* vCard parser for vCard 3.0. See RFC 2426 for more detail.
* </p>
* <p>
* This parser allows vCard format which is not allowed in the RFC, since
* we have seen several vCard 3.0 files which don't comply with it.
* </p>
* <p>
* e.g. vCard 3.0 does not allow "CHARSET" attribute, but some actual files
* have it and they uses non UTF-8 charsets. UTF-8 is recommended in RFC 2426,
* but it is not a must. We silently allow "CHARSET".
* </p>
*/
public class VCardParser_V30 implements VCardParser {
public static final Set<String> sKnownPropertyNameSet =
/* package */ static final Set<String> sKnownPropertyNameSet =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
"BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1
"NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS",
"SORT-STRING", "CATEGORIES", "PRODID"))); // 3.0
// Although "7bit" and "BASE64" is not allowed in vCard 3.0, we allow it for safety.
public static final Set<String> sAcceptableEncoding =
/**
* <p>
* A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 3.0.
* </p>
* <p>
* Though vCard 2.1 specification does not allow "7BIT" or "BASE64", we allow them for safety.
* </p>
* <p>
* "QUOTED-PRINTABLE" is not allowed in vCard 3.0 and not in this parser either,
* because the encoding ambiguates how the vCard file to be parsed.
* </p>
*/
/* package */ static final Set<String> sAcceptableEncoding =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"7BIT", "8BIT", "BASE64", "B")));
VCardConstants.PARAM_ENCODING_7BIT,
VCardConstants.PARAM_ENCODING_8BIT,
VCardConstants.PARAM_ENCODING_BASE64,
VCardConstants.PARAM_ENCODING_B)));
private final VCardParserImpl_V30 mVCardParserImpl;
@@ -56,7 +80,6 @@ public class VCardParser_V30 implements VCardParser {
mVCardParserImpl = new VCardParserImpl_V30(parseType);
}
//// Implemented methods
public boolean parse(InputStream is, VCardInterpreter interepreter)