summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaisuke Miyakawa <>2009-03-24 22:52:27 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-24 22:52:27 -0700
commit52fc2180fa535d12838498e877b6d7557c4024dd (patch)
tree6954b6a5606712b9359db15f2b298aa605ba8d74
parent8c0d68f6695f4a0c98c12314eea81afcd59ea52d (diff)
downloadframeworks_base-52fc2180fa535d12838498e877b6d7557c4024dd.zip
frameworks_base-52fc2180fa535d12838498e877b6d7557c4024dd.tar.gz
frameworks_base-52fc2180fa535d12838498e877b6d7557c4024dd.tar.bz2
Automated import from //branches/donutburger/...@142509,142509
-rw-r--r--core/java/android/syncml/pim/PropertyNode.java137
-rw-r--r--core/java/android/syncml/pim/VBuilder.java13
-rw-r--r--core/java/android/syncml/pim/VDataBuilder.java220
-rw-r--r--core/java/android/syncml/pim/vcard/VCardParser.java3
-rw-r--r--core/java/android/syncml/pim/vcard/VCardParser_V21.java1379
-rw-r--r--core/java/android/syncml/pim/vcard/VCardParser_V30.java348
-rw-r--r--core/java/android/syncml/pim/vcard/VCardVersionException.java29
-rw-r--r--tests/AndroidTests/res/raw/v21_backslash.vcf5
-rw-r--r--tests/AndroidTests/res/raw/v21_complicated.vcf106
-rw-r--r--tests/AndroidTests/res/raw/v21_japanese_1.vcf6
-rw-r--r--tests/AndroidTests/res/raw/v21_japanese_2.vcf10
-rw-r--r--tests/AndroidTests/res/raw/v21_multiple_entry.vcf33
-rw-r--r--tests/AndroidTests/res/raw/v21_simple.vcf4
-rw-r--r--tests/AndroidTests/res/raw/v30_simple.vcf13
-rw-r--r--tests/AndroidTests/src/com/android/unit_tests/VCardTests.java773
15 files changed, 2069 insertions, 1010 deletions
diff --git a/core/java/android/syncml/pim/PropertyNode.java b/core/java/android/syncml/pim/PropertyNode.java
index 13d4930..cc52499 100644
--- a/core/java/android/syncml/pim/PropertyNode.java
+++ b/core/java/android/syncml/pim/PropertyNode.java
@@ -16,25 +16,142 @@
package android.syncml.pim;
-import java.util.ArrayList;
-import java.util.Collection;
-
import android.content.ContentValues;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
public class PropertyNode {
public String propName;
- public String propValue = "";
+ public String propValue;
- public Collection<String> propValue_vector;
+ public List<String> propValue_vector;
- /** Store value as byte[],after decode. */
- public byte[] propValue_byts;
+ /** Store value as byte[],after decode.
+ * Used when propValue is encoded by something like BASE64, QUOTED-PRINTABLE, etc.
+ */
+ public byte[] propValue_bytes;
- /** param store: key=paramType, value=paramValue */
- public ContentValues paraMap = new ContentValues();
+ /** param store: key=paramType, value=paramValue
+ * Note that currently PropertyNode class does not support multiple param-values
+ * defined in vCard 3.0 (See also RFC 2426). multiple-values are stored as
+ * one String value like "A,B", not ["A", "B"]...
+ * TODO: fix this.
+ */
+ public ContentValues paramMap;
/** Only for TYPE=??? param store. */
- public ArrayList<String> paraMap_TYPE = new ArrayList<String>();
+ public Set<String> paramMap_TYPE;
+
+ /** Store group values. Used only in VCard. */
+ public Set<String> propGroupSet;
+
+ public PropertyNode() {
+ propValue = "";
+ paramMap = new ContentValues();
+ paramMap_TYPE = new HashSet<String>();
+ propGroupSet = new HashSet<String>();
+ }
+
+ public PropertyNode(
+ String propName, String propValue, List<String> propValue_vector,
+ byte[] propValue_bytes, ContentValues paramMap, Set<String> paramMap_TYPE,
+ Set<String> propGroupSet) {
+ this.propName = propName;
+ if (propValue != null) {
+ this.propValue = propValue;
+ } else {
+ this.propValue = "";
+ }
+ this.propValue_vector = propValue_vector;
+ this.propValue_bytes = propValue_bytes;
+ if (paramMap != null) {
+ this.paramMap = paramMap;
+ } else {
+ this.paramMap = new ContentValues();
+ }
+ if (paramMap_TYPE != null) {
+ this.paramMap_TYPE = paramMap_TYPE;
+ } else {
+ this.paramMap_TYPE = new HashSet<String>();
+ }
+ if (propGroupSet != null) {
+ this.propGroupSet = propGroupSet;
+ } else {
+ this.propGroupSet = new HashSet<String>();
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PropertyNode)) {
+ return false;
+ }
+
+ PropertyNode node = (PropertyNode)obj;
+
+ if (propName == null || !propName.equals(node.propName)) {
+ return false;
+ } else if (!paramMap.equals(node.paramMap)) {
+ return false;
+ } else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) {
+ return false;
+ } else if (!propGroupSet.equals(node.propGroupSet)) {
+ return false;
+ }
+
+ if (propValue_bytes != null && Arrays.equals(propValue_bytes, node.propValue_bytes)) {
+ return true;
+ } else {
+ // Log.d("@@@", propValue + ", " + node.propValue);
+ if (!propValue.equals(node.propValue)) {
+ return false;
+ }
+
+ // The value in propValue_vector is not decoded even if it should be
+ // decoded by BASE64 or QUOTED-PRINTABLE. When the size of propValue_vector
+ // is 1, the encoded value is stored in propValue, so we do not have to
+ // check it.
+ if (propValue_vector != null) {
+ // Log.d("@@@", "===" + propValue_vector + ", " + node.propValue_vector);
+ return (propValue_vector.equals(node.propValue_vector) ||
+ (propValue_vector.size() == 1));
+ } else if (node.propValue_vector != null) {
+ // Log.d("@@@", "===" + propValue_vector + ", " + node.propValue_vector);
+ return (node.propValue_vector.equals(propValue_vector) ||
+ (node.propValue_vector.size() == 1));
+ } else {
+ return true;
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("propName: ");
+ builder.append(propName);
+ builder.append(", paramMap: ");
+ builder.append(paramMap.toString());
+ builder.append(", propmMap_TYPE: ");
+ builder.append(paramMap_TYPE.toString());
+ builder.append(", propGroupSet: ");
+ builder.append(propGroupSet.toString());
+ if (propValue_vector != null && propValue_vector.size() > 1) {
+ builder.append(", propValue_vector size: ");
+ builder.append(propValue_vector.size());
+ }
+ if (propValue_bytes != null) {
+ builder.append(", propValue_bytes size: ");
+ builder.append(propValue_bytes.length);
+ }
+ builder.append(", propValue: ");
+ builder.append(propValue);
+ return builder.toString();
+ }
}
diff --git a/core/java/android/syncml/pim/VBuilder.java b/core/java/android/syncml/pim/VBuilder.java
index 822c2ce..4528645 100644
--- a/core/java/android/syncml/pim/VBuilder.java
+++ b/core/java/android/syncml/pim/VBuilder.java
@@ -16,7 +16,7 @@
package android.syncml.pim;
-import java.util.Collection;
+import java.util.List;
public interface VBuilder {
void start();
@@ -38,9 +38,14 @@ public interface VBuilder {
void endProperty();
/**
+ * @param group
+ */
+ void propertyGroup(String group);
+
+ /**
* @param name
- * a.N <br>
- * a.N
+ * N <br>
+ * N
*/
void propertyName(String name);
@@ -58,5 +63,5 @@ public interface VBuilder {
*/
void propertyParamValue(String value);
- void propertyValues(Collection<String> values);
+ void propertyValues(List<String> values);
}
diff --git a/core/java/android/syncml/pim/VDataBuilder.java b/core/java/android/syncml/pim/VDataBuilder.java
index f0a0cb9..8c67cf5 100644
--- a/core/java/android/syncml/pim/VDataBuilder.java
+++ b/core/java/android/syncml/pim/VDataBuilder.java
@@ -16,11 +16,19 @@
package android.syncml.pim;
+import android.content.ContentValues;
+import android.util.Log;
+
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.net.QuotedPrintableCodec;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
+import java.util.Vector;
/**
* Store the parse result to custom datastruct: VNode, PropertyNode
@@ -29,14 +37,32 @@ import java.util.Collection;
* PropertyNode: standy by a property line of a card.
*/
public class VDataBuilder implements VBuilder {
+ static private String LOG_TAG = "VDATABuilder";
/** type=VNode */
- public ArrayList<VNode> vNodeList = new ArrayList<VNode>();
- int nodeListPos = 0;
- VNode curVNode;
- PropertyNode curPropNode;
- String curParamType;
+ public List<VNode> vNodeList = new ArrayList<VNode>();
+ private int mNodeListPos = 0;
+ private VNode mCurrentVNode;
+ private PropertyNode mCurrentPropNode;
+ private String mCurrentParamType;
+
+ /**
+ * Assumes that each String can be encoded into byte array using this encoding.
+ */
+ private String mCharset;
+
+ private boolean mStrictLineBreakParsing;
+
+ public VDataBuilder() {
+ mCharset = "ISO-8859-1";
+ mStrictLineBreakParsing = false;
+ }
+ public VDataBuilder(String encoding, boolean strictLineBreakParsing) {
+ mCharset = encoding;
+ mStrictLineBreakParsing = strictLineBreakParsing;
+ }
+
public void start() {
}
@@ -48,79 +74,171 @@ public class VDataBuilder implements VBuilder {
vnode.parseStatus = 1;
vnode.VName = type;
vNodeList.add(vnode);
- nodeListPos = vNodeList.size()-1;
- curVNode = vNodeList.get(nodeListPos);
+ mNodeListPos = vNodeList.size()-1;
+ mCurrentVNode = vNodeList.get(mNodeListPos);
}
public void endRecord() {
- VNode endNode = vNodeList.get(nodeListPos);
+ VNode endNode = vNodeList.get(mNodeListPos);
endNode.parseStatus = 0;
- while(nodeListPos > 0){
- nodeListPos--;
- if((vNodeList.get(nodeListPos)).parseStatus == 1)
+ while(mNodeListPos > 0){
+ mNodeListPos--;
+ if((vNodeList.get(mNodeListPos)).parseStatus == 1)
break;
}
- curVNode = vNodeList.get(nodeListPos);
+ mCurrentVNode = vNodeList.get(mNodeListPos);
}
public void startProperty() {
- // System.out.println("+ startProperty. ");
+ // System.out.println("+ startProperty. ");
}
public void endProperty() {
- // System.out.println("- endProperty. ");
+ // System.out.println("- endProperty. ");
}
-
+
public void propertyName(String name) {
- curPropNode = new PropertyNode();
- curPropNode.propName = name;
+ mCurrentPropNode = new PropertyNode();
+ mCurrentPropNode.propName = name;
}
+ // Used only in VCard.
+ public void propertyGroup(String group) {
+ mCurrentPropNode.propGroupSet.add(group);
+ }
+
public void propertyParamType(String type) {
- curParamType = type;
+ mCurrentParamType = type;
}
public void propertyParamValue(String value) {
- if(curParamType == null)
- curPropNode.paraMap_TYPE.add(value);
- else if(curParamType.equalsIgnoreCase("TYPE"))
- curPropNode.paraMap_TYPE.add(value);
- else
- curPropNode.paraMap.put(curParamType, value);
-
- curParamType = null;
- }
-
- public void propertyValues(Collection<String> values) {
- curPropNode.propValue_vector = values;
- curPropNode.propValue = listToString(values);
- //decode value string to propValue_byts
- if(curPropNode.paraMap.containsKey("ENCODING")){
- if(curPropNode.paraMap.getAsString("ENCODING").
- equalsIgnoreCase("BASE64")){
- curPropNode.propValue_byts =
- Base64.decodeBase64(curPropNode.propValue.
+ if (mCurrentParamType == null ||
+ mCurrentParamType.equalsIgnoreCase("TYPE")) {
+ mCurrentPropNode.paramMap_TYPE.add(value);
+ } else {
+ mCurrentPropNode.paramMap.put(mCurrentParamType, value);
+ }
+
+ mCurrentParamType = null;
+ }
+
+ private String encodeString(String originalString, String targetEncoding) {
+ Charset charset = Charset.forName(mCharset);
+ ByteBuffer byteBuffer = charset.encode(originalString);
+ // byteBuffer.array() "may" return byte array which is larger than
+ // byteBuffer.remaining(). Here, we keep on the safe side.
+ byte[] bytes = new byte[byteBuffer.remaining()];
+ byteBuffer.get(bytes);
+ try {
+ return new String(bytes, targetEncoding);
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ }
+ }
+
+ public void propertyValues(List<String> values) {
+ ContentValues paramMap = mCurrentPropNode.paramMap;
+
+ String charsetString = paramMap.getAsString("CHARSET");
+
+ boolean setupParamValues = false;
+ //decode value string to propValue_bytes
+ if (paramMap.containsKey("ENCODING")) {
+ String encoding = paramMap.getAsString("ENCODING");
+ if (encoding.equalsIgnoreCase("BASE64") ||
+ encoding.equalsIgnoreCase("B")) {
+ if (values.size() > 1) {
+ Log.e(LOG_TAG,
+ ("BASE64 encoding is used while " +
+ "there are multiple values (" + values.size()));
+ }
+ mCurrentPropNode.propValue_bytes =
+ Base64.decodeBase64(values.get(0).
replaceAll(" ","").replaceAll("\t","").
replaceAll("\r\n","").
getBytes());
}
- if(curPropNode.paraMap.getAsString("ENCODING").
- equalsIgnoreCase("QUOTED-PRINTABLE")){
+
+ if(encoding.equalsIgnoreCase("QUOTED-PRINTABLE")){
+ // if CHARSET is defined, we translate each String into the Charset.
+ List<String> tmpValues = new ArrayList<String>();
+ Vector<byte[]> byteVector = new Vector<byte[]>();
+ int size = 0;
try{
- curPropNode.propValue_byts =
- QuotedPrintableCodec.decodeQuotedPrintable(
- curPropNode.propValue.
- replaceAll("= ", " ").replaceAll("=\t", "\t").
- getBytes() );
- curPropNode.propValue =
- new String(curPropNode.propValue_byts);
- }catch(Exception e){
- System.out.println("=Decode quoted-printable exception.");
- e.printStackTrace();
+ for (String value : values) {
+ String quotedPrintable = value
+ .replaceAll("= ", " ").replaceAll("=\t", "\t");
+ String[] lines;
+ if (mStrictLineBreakParsing) {
+ lines = quotedPrintable.split("\r\n");
+ } else {
+ lines = quotedPrintable
+ .replace("\r\n", "\n").replace("\r", "\n").split("\n");
+ }
+ StringBuilder builder = new StringBuilder();
+ for (String line : lines) {
+ if (line.endsWith("=")) {
+ line = line.substring(0, line.length() - 1);
+ }
+ builder.append(line);
+ }
+ byte[] bytes = QuotedPrintableCodec.decodeQuotedPrintable(
+ builder.toString().getBytes());
+ if (charsetString != null) {
+ try {
+ tmpValues.add(new String(bytes, charsetString));
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + charsetString);
+ tmpValues.add(new String(bytes));
+ }
+ } else {
+ tmpValues.add(new String(bytes));
+ }
+ byteVector.add(bytes);
+ size += bytes.length;
+ } // for (String value : values) {
+ mCurrentPropNode.propValue_vector = tmpValues;
+ mCurrentPropNode.propValue = listToString(tmpValues);
+
+ mCurrentPropNode.propValue_bytes = new byte[size];
+
+ {
+ byte[] tmpBytes = mCurrentPropNode.propValue_bytes;
+ int index = 0;
+ for (byte[] bytes : byteVector) {
+ int length = bytes.length;
+ for (int i = 0; i < length; i++, index++) {
+ tmpBytes[index] = bytes[i];
+ }
+ }
+ }
+ setupParamValues = true;
+ } catch(Exception e) {
+ Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e);
}
+ } // QUOTED-PRINTABLE
+ } // ENCODING
+
+ if (!setupParamValues) {
+ // if CHARSET is defined, we translate each String into the Charset.
+ if (charsetString != null) {
+ List<String> tmpValues = new ArrayList<String>();
+ for (String value : values) {
+ String result = encodeString(value, charsetString);
+ if (result != null) {
+ tmpValues.add(result);
+ } else {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + charsetString);
+ tmpValues.add(value);
+ }
+ }
+ values = tmpValues;
}
+
+ mCurrentPropNode.propValue_vector = values;
+ mCurrentPropNode.propValue = listToString(values);
}
- curVNode.propList.add(curPropNode);
+ mCurrentVNode.propList.add(mCurrentPropNode);
}
private String listToString(Collection<String> list){
@@ -134,7 +252,7 @@ public class VDataBuilder implements VBuilder {
}
return typeListB.toString();
}
-
+
public String getResult(){
return null;
}
diff --git a/core/java/android/syncml/pim/vcard/VCardParser.java b/core/java/android/syncml/pim/vcard/VCardParser.java
index 3926243..6dad852d 100644
--- a/core/java/android/syncml/pim/vcard/VCardParser.java
+++ b/core/java/android/syncml/pim/vcard/VCardParser.java
@@ -26,7 +26,8 @@ import java.io.IOException;
public class VCardParser {
- VParser mParser = null;
+ // TODO: fix this.
+ VCardParser_V21 mParser = null;
public final static String VERSION_VCARD21 = "vcard2.1";
diff --git a/core/java/android/syncml/pim/vcard/VCardParser_V21.java b/core/java/android/syncml/pim/vcard/VCardParser_V21.java
index b6fa032..f853c5e 100644
--- a/core/java/android/syncml/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/syncml/pim/vcard/VCardParser_V21.java
@@ -16,21 +16,24 @@
package android.syncml.pim.vcard;
-import android.syncml.pim.VParser;
+import android.syncml.pim.VBuilder;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class is used to parse vcard. Please refer to vCard Specification 2.1
*/
-public class VCardParser_V21 extends VParser {
+public class VCardParser_V21 {
/** Store the known-type */
- private static final HashSet<String> mKnownTypeSet = new HashSet<String>(
+ private static final HashSet<String> sKnownTypeSet = new HashSet<String>(
Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
"PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
"MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
@@ -39,13 +42,40 @@ public class VCardParser_V21 extends VParser {
"CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
"PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
"WAVE", "AIFF", "PCM", "X509", "PGP"));
-
- /** Store the name */
- private static final HashSet<String> mName = new HashSet<String>(Arrays
- .asList("LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
- "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
- "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER"));
-
+
+ /** Store the known-value */
+ private static final HashSet<String> sKnownValueSet = new HashSet<String>(
+ Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID"));
+
+ /** Store the property name available in vCard 2.1 */
+ // NICKNAME is not supported in vCard 2.1, but some vCard may contain.
+ private static final HashSet<String> sAvailablePropertyNameV21 =
+ new HashSet<String>(Arrays.asList(
+ "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
+ "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
+ "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER",
+ "NICKNAME"));
+
+ // Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
+ // We allow it for safety...
+ private static final HashSet<String> sAvailableEncodingV21 =
+ new HashSet<String>(Arrays.asList(
+ "7BIT", "8BIT", "QUOTED-PRINTABLE", "BASE64", "B"));
+
+ // Used only for parsing END:VCARD.
+ private String mPreviousLine;
+
+ /** The builder to build parsed data */
+ protected VBuilder mBuilder = null;
+
+ /** The encoding type */
+ protected String mEncoding = null;
+
+ protected final String sDefaultEncoding = "8BIT";
+
+ // Should not directly read a line from this. Use getLine() instead.
+ protected BufferedReader mReader;
+
/**
* Create a new VCard parser.
*/
@@ -55,916 +85,597 @@ public class VCardParser_V21 extends VParser {
/**
* Parse the file at the given position
- *
- * @param offset
- * the given position to parse
- * @return vcard length
+ * vcard_file = [wsls] vcard [wsls]
*/
- protected int parseVFile(int offset) {
- return parseVCardFile(offset);
+ protected void parseVCardFile() throws IOException, VCardException {
+ while (parseOneVCard()) {
+ }
}
+ protected String getVersion() {
+ return "2.1";
+ }
+
/**
- * [wsls] vcard [wsls]
+ * @return true when the propertyName is a valid property name.
*/
- int parseVCardFile(int offset) {
- int ret = 0, sum = 0;
-
- /* remove \t \r\n */
- while ((ret = parseWsls(offset)) != PARSE_ERROR) {
- offset += ret;
- sum += ret;
- }
-
- ret = parseVCard(offset); // BEGIN:VCARD ... END:VCARD
- if (ret != PARSE_ERROR) {
- offset += ret;
- sum += ret;
- } else {
- return PARSE_ERROR;
- }
+ protected boolean isValidPropertyName(String propertyName) {
+ return sAvailablePropertyNameV21.contains(propertyName.toUpperCase());
+ }
- /* remove \t \r\n */
- while ((ret = parseWsls(offset)) != PARSE_ERROR) {
- offset += ret;
- sum += ret;
+ /**
+ * @return true when the encoding is a valid encoding.
+ */
+ protected boolean isValidEncoding(String encoding) {
+ return sAvailableEncodingV21.contains(encoding.toUpperCase());
+ }
+
+ /**
+ * @return String. It may be null, or its length may be 0
+ * @throws IOException
+ */
+ protected String getLine() throws IOException {
+ return mReader.readLine();
+ }
+
+ /**
+ * @return String with it's length > 0
+ * @throws IOException
+ * @throws VCardException when the stream reached end of line
+ */
+ protected String getNonEmptyLine() throws IOException, VCardException {
+ String line;
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException("Reached end of buffer.");
+ } else if (line.trim().length() > 0) {
+ return line;
+ }
}
- return sum;
}
-
+
/**
- * "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":"
- * "VCARD"
+ * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
+ * items *CRLF
+ * "END" [ws] ":" [ws] "VCARD"
*/
- private int parseVCard(int offset) {
- int ret = 0, sum = 0;
-
- /* BEGIN */
- ret = parseString(offset, "BEGIN", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
+ private boolean parseOneVCard() throws IOException, VCardException {
+ if (!readBeginVCard()) {
+ return false;
}
- offset += ret;
- sum += ret;
-
- /* [ws] */
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- /* colon */
- ret = parseString(offset, ":", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
+ parseItems();
+ readEndVCard();
+ return true;
+ }
+
+ /**
+ * @return True when successful. False when reaching the end of line
+ * @throws IOException
+ * @throws VCardException
+ */
+ protected boolean readBeginVCard() throws IOException, VCardException {
+ String line;
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ return false;
+ } else if (line.trim().length() > 0) {
+ break;
+ }
}
- offset += ret;
- sum += ret;
-
- /* [ws] */
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- /* VCARD */
- ret = parseString(offset, "VCARD", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
+ String[] strArray = line.split(":", 2);
+
+ // Though vCard specification does not allow lower cases,
+ // some data may have them, so we allow it.
+ if (!(strArray.length == 2 &&
+ strArray[0].trim().equalsIgnoreCase("BEGIN") &&
+ strArray[1].trim().equalsIgnoreCase("VCARD"))) {
+ throw new VCardException("BEGIN:VCARD != \"" + line + "\"");
}
- offset += ret;
- sum += ret;
+
if (mBuilder != null) {
mBuilder.startRecord("VCARD");
}
- /* [ws] */
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- /* 1*CRLF */
- ret = parseCrlf(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- while ((ret = parseCrlf(offset)) != PARSE_ERROR) {
- offset += ret;
- sum += ret;
- }
-
- ret = parseItems(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
-
- /* *CRLF */
- while ((ret = parseCrlf(offset)) != PARSE_ERROR) {
- offset += ret;
- sum += ret;
- }
-
- /* END */
- ret = parseString(offset, "END", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
-
- /* [ws] */
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- /* colon */
- ret = parseString(offset, ":", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
-
- /* [ws] */
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- /* VCARD */
- ret = parseString(offset, "VCARD", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- // offset += ret;
- sum += ret;
+ return true;
+ }
+
+ protected void readEndVCard() throws VCardException {
+ // Though vCard specification does not allow lower cases,
+ // some data may have them, so we allow it.
+ String[] strArray = mPreviousLine.split(":", 2);
+ if (!(strArray.length == 2 &&
+ strArray[0].trim().equalsIgnoreCase("END") &&
+ strArray[1].trim().equalsIgnoreCase("VCARD"))) {
+ throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
+ }
+
if (mBuilder != null) {
mBuilder.endRecord();
}
-
- return sum;
}
-
+
/**
- * items *CRLF item / item
+ * items = *CRLF item
+ * / item
*/
- private int parseItems(int offset) {
+ protected void parseItems() throws IOException, VCardException {
/* items *CRLF item / item */
- int ret = 0, sum = 0;
-
+ boolean ended = false;
+
if (mBuilder != null) {
mBuilder.startProperty();
}
- ret = parseItem(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- mBuilder.endProperty();
- }
- for (;;) {
- while ((ret = parseCrlf(offset)) != PARSE_ERROR) {
- offset += ret;
- sum += ret;
+ try {
+ ended = parseItem();
+ } finally {
+ if (mBuilder != null) {
+ mBuilder.endProperty();
}
+ }
+
+ while (!ended) {
// follow VCARD ,it wont reach endProperty
if (mBuilder != null) {
mBuilder.startProperty();
}
-
- ret = parseItem(offset);
- if (ret == PARSE_ERROR) {
- break;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- mBuilder.endProperty();
+ try {
+ ended = parseItem();
+ } finally {
+ if (mBuilder != null) {
+ mBuilder.endProperty();
+ }
}
}
-
- return sum;
}
/**
- * item0 / item1 / item2
+ * item = [groups "."] name [params] ":" value CRLF
+ * / [groups "."] "ADR" [params] ":" addressparts CRLF
+ * / [groups "."] "ORG" [params] ":" orgparts CRLF
+ * / [groups "."] "N" [params] ":" nameparts CRLF
+ * / [groups "."] "AGENT" [params] ":" vcard CRLF
*/
- private int parseItem(int offset) {
- int ret = 0, sum = 0;
- mEncoding = mDefaultEncoding;
-
- ret = parseItem0(offset);
- if (ret != PARSE_ERROR) {
- sum += ret;
- return sum;
- }
-
- ret = parseItem1(offset);
- if (ret != PARSE_ERROR) {
- sum += ret;
- return sum;
- }
-
- ret = parseItem2(offset);
- if (ret != PARSE_ERROR) {
- sum += ret;
- return sum;
- }
-
- return PARSE_ERROR;
- }
-
- /** [groups "."] name [params] ":" value CRLF */
- private int parseItem0(int offset) {
- int ret = 0, sum = 0, start = offset;
- String proName = "", proValue = "";
-
- ret = parseGroupsWithDot(offset);
- if (ret != PARSE_ERROR) {
- offset += ret;
- sum += ret;
- }
-
- ret = parseName(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- proName = mBuffer.substring(start, offset).trim();
- mBuilder.propertyName(proName);
- }
-
- ret = parseParams(offset);
- if (ret != PARSE_ERROR) {
- offset += ret;
- sum += ret;
- }
-
- ret = parseString(offset, ":", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
-
- start = offset;
- ret = parseValue(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- proValue = mBuffer.substring(start, offset);
- if (proName.equals("VERSION") && !proValue.equals("2.1")) {
- return PARSE_ERROR;
- }
- if (mBuilder != null) {
- ArrayList<String> v = new ArrayList<String>();
- v.add(proValue);
- mBuilder.propertyValues(v);
- }
-
- ret = parseCrlf(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- sum += ret;
-
- return sum;
- }
-
- /** "ADR" "ORG" "N" with semi-colon separated content */
- private int parseItem1(int offset) {
- int ret = 0, sum = 0, start = offset;
-
- ret = parseGroupsWithDot(offset);
- if (ret != PARSE_ERROR) {
- offset += ret;
- sum += ret;
- }
-
- if ((ret = parseString(offset, "ADR", true)) == PARSE_ERROR
- && (ret = parseString(offset, "ORG", true)) == PARSE_ERROR
- && (ret = parseString(offset, "N", true)) == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
+ protected boolean parseItem() throws IOException, VCardException {
+ mEncoding = sDefaultEncoding;
+
+ // params = ";" [ws] paramlist
+ String line = getNonEmptyLine();
+ String[] strArray = line.split(":", 2);
+ if (strArray.length < 2) {
+ throw new VCardException("Invalid line(\":\" does not exist): " + line);
+ }
+ String propertyValue = strArray[1];
+ String[] groupNameParamsArray = strArray[0].split(";");
+ String groupAndName = groupNameParamsArray[0].trim();
+ String[] groupNameArray = groupAndName.split("\\.");
+ int length = groupNameArray.length;
+ String propertyName = groupNameArray[length - 1];
if (mBuilder != null) {
- mBuilder.propertyName(mBuffer.substring(start, offset).trim());
- }
-
- ret = parseParams(offset);
- if (ret != PARSE_ERROR) {
- offset += ret;
- sum += ret;
- }
-
- ret = parseString(offset, ":", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
-
- start = offset;
- ret = parseValue(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- int end = 0;
- ArrayList<String> v = new ArrayList<String>();
- Pattern p = Pattern
- .compile("([^;\\\\]*(\\\\[\\\\;:,])*[^;\\\\]*)(;?)");
- Matcher m = p.matcher(mBuffer.substring(start, offset));
- while (m.find()) {
- String s = escapeTranslator(m.group(1));
- v.add(s);
- end = m.end();
- if (offset == start + end) {
- String endValue = m.group(3);
- if (";".equals(endValue)) {
- v.add("");
- }
- break;
- }
+ mBuilder.propertyName(propertyName);
+ for (int i = 0; i < length - 1; i++) {
+ mBuilder.propertyGroup(groupNameArray[i]);
}
- mBuilder.propertyValues(v);
}
-
- ret = parseCrlf(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- sum += ret;
-
- return sum;
+ if (propertyName.equalsIgnoreCase("END")) {
+ mPreviousLine = line;
+ return true;
+ }
+
+ length = groupNameParamsArray.length;
+ for (int i = 1; i < length; i++) {
+ handleParams(groupNameParamsArray[i]);
+ }
+
+ if (isValidPropertyName(propertyName) ||
+ propertyName.startsWith("X-")) {
+ if (propertyName.equals("VERSION") &&
+ !propertyValue.equals(getVersion())) {
+ throw new VCardVersionException("Incompatible version: " +
+ propertyValue + " != " + getVersion());
+ }
+ handlePropertyValue(propertyName, propertyValue);
+ return false;
+ } else if (propertyName.equals("ADR") ||
+ propertyName.equals("ORG") ||
+ propertyName.equals("N")) {
+ handleMultiplePropertyValue(propertyName, propertyValue);
+ return false;
+ } else if (propertyName.equals("AGENT")) {
+ handleAgent(propertyValue);
+ return false;
+ }
+
+ throw new VCardException("Unknown property name: \"" +
+ propertyName + "\"");
}
- /** [groups] "." "AGENT" [params] ":" vcard CRLF */
- private int parseItem2(int offset) {
- int ret = 0, sum = 0, start = offset;
-
- ret = parseGroupsWithDot(offset);
- if (ret != PARSE_ERROR) {
- offset += ret;
- sum += ret;
- }
-
- ret = parseString(offset, "AGENT", true);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- mBuilder.propertyName(mBuffer.substring(start, offset));
- }
-
- ret = parseParams(offset);
- if (ret != PARSE_ERROR) {
- offset += ret;
- sum += ret;
- }
-
- ret = parseString(offset, ":", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
-
- ret = parseCrlf(offset);
- if (ret != PARSE_ERROR) {
- offset += ret;
- sum += ret;
- }
-
- ret = parseVCard(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- mBuilder.propertyValues(new ArrayList<String>());
- }
-
- ret = parseCrlf(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
+ /**
+ * params = ";" [ws] paramlist
+ * paramlist = paramlist [ws] ";" [ws] param
+ * / param
+ * param = "TYPE" [ws] "=" [ws] ptypeval
+ * / "VALUE" [ws] "=" [ws] pvalueval
+ * / "ENCODING" [ws] "=" [ws] pencodingval
+ * / "CHARSET" [ws] "=" [ws] charsetval
+ * / "LANGUAGE" [ws] "=" [ws] langval
+ * / "X-" word [ws] "=" [ws] word
+ * / knowntype
+ */
+ protected void handleParams(String params) throws VCardException {
+ String[] strArray = params.split("=", 2);
+ if (strArray.length == 2) {
+ String paramName = strArray[0].trim();
+ String paramValue = strArray[1].trim();
+ if (paramName.equals("TYPE")) {
+ handleType(paramValue);
+ } else if (paramName.equals("VALUE")) {
+ handleValue(paramValue);
+ } else if (paramName.equals("ENCODING")) {
+ handleEncoding(paramValue);
+ } else if (paramName.equals("CHARSET")) {
+ handleCharset(paramValue);
+ } else if (paramName.equals("LANGUAGE")) {
+ handleLanguage(paramValue);
+ } else if (paramName.startsWith("X-")) {
+ handleAnyParam(paramName, paramValue);
+ } else {
+ throw new VCardException("Unknown type \"" + paramName + "\"");
+ }
+ } else {
+ handleType(strArray[0]);
}
- sum += ret;
-
- return sum;
}
-
- private int parseGroupsWithDot(int offset) {
- int ret = 0, sum = 0;
- /* [groups "."] */
- ret = parseGroups(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
-
- ret = parseString(offset, ".", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- sum += ret;
-
- return sum;
+
+ /**
+ * typeval = knowntype / "X-" word
+ */
+ protected void handleType(String ptypeval) throws VCardException {
+ if (sKnownTypeSet.contains(ptypeval.toUpperCase()) ||
+ ptypeval.startsWith("X-")) {
+ if (mBuilder != null) {
+ mBuilder.propertyParamType("TYPE");
+ mBuilder.propertyParamValue(ptypeval.toUpperCase());
+ }
+ } else {
+ throw new VCardException("Unknown type: \"" + ptypeval + "\"");
+ }
}
-
- /** ";" [ws] paramlist */
- private int parseParams(int offset) {
- int ret = 0, sum = 0;
-
- ret = parseString(offset, ";", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
-
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- ret = parseParamList(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
+
+ /**
+ * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
+ */
+ protected void handleValue(String pvalueval) throws VCardException {
+ if (sKnownValueSet.contains(pvalueval.toUpperCase()) ||
+ pvalueval.startsWith("X-")) {
+ if (mBuilder != null) {
+ mBuilder.propertyParamType("VALUE");
+ mBuilder.propertyParamValue(pvalueval);
+ }
+ } else {
+ throw new VCardException("Unknown value \"" + pvalueval + "\"");
}
- sum += ret;
-
- return sum;
}
-
+
/**
- * paramlist [ws] ";" [ws] param / param
+ * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
*/
- private int parseParamList(int offset) {
- int ret = 0, sum = 0;
-
- ret = parseParam(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
-
- int offsetTemp = offset;
- int sumTemp = sum;
- for (;;) {
- ret = removeWs(offsetTemp);
- offsetTemp += ret;
- sumTemp += ret;
-
- ret = parseString(offsetTemp, ";", false);
- if (ret == PARSE_ERROR) {
- return sum;
- }
- offsetTemp += ret;
- sumTemp += ret;
-
- ret = removeWs(offsetTemp);
- offsetTemp += ret;
- sumTemp += ret;
-
- ret = parseParam(offsetTemp);
- if (ret == PARSE_ERROR) {
- break;
+ protected void handleEncoding(String pencodingval) throws VCardException {
+ if (isValidEncoding(pencodingval) ||
+ pencodingval.startsWith("X-")) {
+ if (mBuilder != null) {
+ mBuilder.propertyParamType("ENCODING");
+ mBuilder.propertyParamValue(pencodingval);
}
- offsetTemp += ret;
- sumTemp += ret;
-
- // offset = offsetTemp;
- sum = sumTemp;
+ mEncoding = pencodingval;
+ } else {
+ throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
}
- return sum;
}
-
+
/**
- * param0 / param1 / param2 / param3 / param4 / param5 / knowntype<BR>
- * TYPE / VALUE / ENDCODING / CHARSET / LANGUAGE ...
+ * vCard specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
+ * but some vCard contains other charset, so we allow them.
*/
- private int parseParam(int offset) {
- int ret = 0, sum = 0;
-
- ret = parseParam0(offset);
- if (ret != PARSE_ERROR) {
- sum += ret;
- return sum;
- }
-
- ret = parseParam1(offset);
- if (ret != PARSE_ERROR) {
- sum += ret;
- return sum;
- }
-
- ret = parseParam2(offset);
- if (ret != PARSE_ERROR) {
- sum += ret;
- return sum;
- }
-
- ret = parseParam3(offset);
- if (ret != PARSE_ERROR) {
- sum += ret;
- return sum;
- }
-
- ret = parseParam4(offset);
- if (ret != PARSE_ERROR) {
- sum += ret;
- return sum;
- }
-
- ret = parseParam5(offset);
- if (ret != PARSE_ERROR) {
- sum += ret;
- return sum;
- }
-
- int start = offset;
- ret = parseKnownType(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
+ protected void handleCharset(String charsetval) {
if (mBuilder != null) {
- mBuilder.propertyParamType(null);
- mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ mBuilder.propertyParamType("CHARSET");
+ mBuilder.propertyParamValue(charsetval);
}
-
- return sum;
}
-
- /** "TYPE" [ws] "=" [ws] ptypeval */
- private int parseParam0(int offset) {
- int ret = 0, sum = 0, start = offset;
-
- ret = parseString(offset, "TYPE", true);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- mBuilder.propertyParamType(mBuffer.substring(start, offset));
- }
-
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- ret = parseString(offset, "=", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
+
+ /**
+ * See also Section 7.1 of RFC 1521
+ */
+ protected void handleLanguage(String langval) throws VCardException {
+ String[] strArray = langval.split("-");
+ if (strArray.length != 2) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ String tmp = strArray[0];
+ int length = tmp.length();
+ for (int i = 0; i < length; i++) {
+ if (!isLetter(tmp.charAt(i))) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
}
- offset += ret;
- sum += ret;
-
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- start = offset;
- ret = parsePTypeVal(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
+ tmp = strArray[1];
+ length = tmp.length();
+ for (int i = 0; i < length; i++) {
+ if (!isLetter(tmp.charAt(i))) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
}
- offset += ret;
- sum += ret;
if (mBuilder != null) {
- mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ mBuilder.propertyParamType("LANGUAGE");
+ mBuilder.propertyParamValue(langval);
}
-
- return sum;
-
}
- /** "VALUE" [ws] "=" [ws] pvalueval */
- private int parseParam1(int offset) {
- int ret = 0, sum = 0, start = offset;
-
- ret = parseString(offset, "VALUE", true);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- mBuilder.propertyParamType(mBuffer.substring(start, offset));
- }
-
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- ret = parseString(offset, "=", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
-
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- start = offset;
- ret = parsePValueVal(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
+ /**
+ * Mainly for "X-" type. This accepts any kind of type without check.
+ */
+ protected void handleAnyParam(String paramName, String paramValue) {
if (mBuilder != null) {
- mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ mBuilder.propertyParamType(paramName);
+ mBuilder.propertyParamValue(paramValue);
}
-
- return sum;
}
-
- /** "ENCODING" [ws] "=" [ws] pencodingval */
- private int parseParam2(int offset) {
- int ret = 0, sum = 0, start = offset;
-
- ret = parseString(offset, "ENCODING", true);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- mBuilder.propertyParamType(mBuffer.substring(start, offset));
- }
-
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- ret = parseString(offset, "=", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
-
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- start = offset;
- ret = parsePEncodingVal(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+
+ protected void handlePropertyValue(
+ String propertyName, String propertyValue) throws
+ IOException, VCardException {
+ if (mEncoding == null || mEncoding.equalsIgnoreCase("7BIT")
+ || mEncoding.equalsIgnoreCase("8BIT")
+ || mEncoding.toUpperCase().startsWith("X-")) {
+ if (mBuilder != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(maybeUnescapeText(propertyValue));
+ mBuilder.propertyValues(v);
+ }
+ } else if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+ String result = getQuotedPrintable(propertyValue);
+ if (mBuilder != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(result);
+ mBuilder.propertyValues(v);
+ }
+ } else if (mEncoding.equalsIgnoreCase("BASE64") ||
+ mEncoding.equalsIgnoreCase("B")) {
+ String result = getBase64(propertyValue);
+ if (mBuilder != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(result);
+ mBuilder.propertyValues(v);
+ }
+ } else {
+ throw new VCardException("Unknown encoding: \"" + mEncoding + "\"");
}
-
- return sum;
-
}
-
- /** "CHARSET" [ws] "=" [ws] charsetval */
- private int parseParam3(int offset) {
- int ret = 0, sum = 0, start = offset;
-
- ret = parseString(offset, "CHARSET", true);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- mBuilder.propertyParamType(mBuffer.substring(start, offset));
- }
-
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- ret = parseString(offset, "=", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
-
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- start = offset;
- ret = parseCharsetVal(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+
+ protected String getQuotedPrintable(String firstString) throws IOException, VCardException {
+ // Specifically, there may be some padding between = and CRLF.
+ // See the following:
+ //
+ // qp-line := *(qp-segment transport-padding CRLF)
+ // qp-part transport-padding
+ // qp-segment := qp-section *(SPACE / TAB) "="
+ // ; Maximum length of 76 characters
+ //
+ // e.g. (from RFC 2045)
+ // Now's the time =
+ // for all folk to come=
+ // to the aid of their country.
+ if (firstString.trim().endsWith("=")) {
+ // remove "transport-padding"
+ int pos = firstString.length() - 1;
+ while(firstString.charAt(pos) != '=') {
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append(firstString.substring(0, pos + 1));
+ builder.append("\r\n");
+ String line;
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException(
+ "File ended during parsing quoted-printable String");
+ }
+ if (line.trim().endsWith("=")) {
+ // remove "transport-padding"
+ pos = line.length() - 1;
+ while(line.charAt(pos) != '=') {
+ }
+ builder.append(line.substring(0, pos + 1));
+ builder.append("\r\n");
+ } else {
+ builder.append(line);
+ break;
+ }
+ }
+ return builder.toString();
+ } else {
+ return firstString;
}
-
- return sum;
}
-
- /** "LANGUAGE" [ws] "=" [ws] langval */
- private int parseParam4(int offset) {
- int ret = 0, sum = 0, start = offset;
-
- ret = parseString(offset, "LANGUAGE", true);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- mBuilder.propertyParamType(mBuffer.substring(start, offset));
- }
-
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- ret = parseString(offset, "=", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
-
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- start = offset;
- ret = parseLangVal(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+
+ protected String getBase64(String firstString) throws IOException, VCardException {
+ StringBuilder builder = new StringBuilder();
+ builder.append(firstString);
+
+ while (true) {
+ String line = getLine();
+ if (line == null) {
+ throw new VCardException(
+ "File ended during parsing BASE64 binary");
+ }
+ if (line.length() == 0) {
+ break;
+ }
+ builder.append(line);
}
-
- return sum;
-
+
+ return builder.toString();
}
-
- /** "X-" word [ws] "=" [ws] word */
- private int parseParam5(int offset) {
- int ret = 0, sum = 0, start = offset;
-
- ret = parseXWord(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
- if (mBuilder != null) {
- mBuilder.propertyParamType(mBuffer.substring(start, offset));
- }
-
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- ret = parseString(offset, "=", false);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
+
+ /**
+ * Mainly for "ADR", "ORG", and "N"
+ * We do not care the number of strnosemi here.
+ *
+ * addressparts = 0*6(strnosemi ";") strnosemi
+ * ; PO Box, Extended Addr, Street, Locality, Region,
+ * Postal Code, Country Name
+ * orgparts = *(strnosemi ";") strnosemi
+ * ; First is Organization Name,
+ * remainder are Organization Units.
+ * nameparts = 0*4(strnosemi ";") strnosemi
+ * ; Family, Given, Middle, Prefix, Suffix.
+ * ; Example:Public;John;Q.;Reverend Dr.;III, Esq.
+ * strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi
+ * ; To include a semicolon in this string, it must be escaped
+ * ; with a "\" character.
+ *
+ * We are not sure whether we should add "\" CRLF to each value.
+ * For now, we exclude them.
+ */
+ protected void handleMultiplePropertyValue(
+ String propertyName, String propertyValue) throws IOException, VCardException {
+ // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some data have it.
+ if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+ propertyValue = getQuotedPrintable(propertyValue);
+ }
+
+ if (propertyValue.endsWith("\\")) {
+ StringBuilder builder = new StringBuilder();
+ // builder.append(propertyValue);
+ builder.append(propertyValue.substring(0, propertyValue.length() - 1));
+ try {
+ String line;
+ while (true) {
+ line = getNonEmptyLine();
+ // builder.append("\r\n");
+ // builder.append(line);
+ if (!line.endsWith("\\")) {
+ builder.append(line);
+ break;
+ } else {
+ builder.append(line.substring(0, line.length() - 1));
+ }
+ }
+ } catch (IOException e) {
+ throw new VCardException(
+ "IOException is throw during reading propertyValue" + e);
+ }
+ // Now, propertyValue may contain "\r\n"
+ propertyValue = builder.toString();
}
- offset += ret;
- sum += ret;
- ret = removeWs(offset);
- offset += ret;
- sum += ret;
-
- start = offset;
- ret = parseWord(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
- }
- offset += ret;
- sum += ret;
if (mBuilder != null) {
- mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ // In String#replaceAll() and Pattern class, "\\\\" means single slash.
+
+ final String IMPOSSIBLE_STRING = "\0";
+ // First replace two backslashes with impossible strings.
+ propertyValue = propertyValue.replaceAll("\\\\\\\\", IMPOSSIBLE_STRING);
+
+ // Now, split propertyValue with ; whose previous char is not back slash.
+ Pattern pattern = Pattern.compile("(?<!\\\\);");
+ // TODO: limit should be set in accordance with propertyName?
+ String[] strArray = pattern.split(propertyValue, -1);
+ ArrayList<String> arrayList = new ArrayList<String>();
+ for (String str : strArray) {
+ // Replace impossible strings with original two backslashes
+ arrayList.add(
+ unescapeText(str.replaceAll(IMPOSSIBLE_STRING, "\\\\\\\\")));
+ }
+ mBuilder.propertyValues(arrayList);
}
-
- return sum;
}
-
+
/**
- * knowntype: "DOM" / "INTL" / ...
+ * vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all.
*/
- private int parseKnownType(int offset) {
- String word = getWord(offset);
-
- if (mKnownTypeSet.contains(word.toUpperCase())) {
- return word.length();
- }
- return PARSE_ERROR;
+ protected void handleAgent(String propertyValue) throws IOException, VCardException {
+ String[] strArray = propertyValue.split(":", 2);
+ if (!(strArray.length == 2 ||
+ strArray[0].trim().equalsIgnoreCase("BEGIN") &&
+ strArray[1].trim().equalsIgnoreCase("VCARD"))) {
+ throw new VCardException("BEGIN:VCARD != \"" + propertyValue + "\"");
+ }
+ parseItems();
+ readEndVCard();
}
-
- /** knowntype / "X-" word */
- private int parsePTypeVal(int offset) {
- int ret = 0, sum = 0;
-
- ret = parseKnownType(offset);
- if (ret != PARSE_ERROR) {
- sum += ret;
- return sum;
- }
-
- ret = parseXWord(offset);
- if (ret != PARSE_ERROR) {
- sum += ret;
- return sum;
- }
- sum += ret;
-
- return sum;
+
+ /**
+ * For vCard 3.0.
+ */
+ protected String maybeUnescapeText(String text) {
+ return text;
}
-
- /** "LOGO" /.../ XWord, case insensitive */
- private int parseName(int offset) {
- int ret = 0;
- ret = parseXWord(offset);
- if (ret != PARSE_ERROR) {
- return ret;
- }
- String word = getWord(offset).toUpperCase();
- if (mName.contains(word)) {
- return word.length();
- }
- return PARSE_ERROR;
+
+ /**
+ * Convert escaped text into unescaped text.
+ */
+ protected String unescapeText(String text) {
+ // Original vCard 2.1 specification does not allow transformation
+ // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of
+ // this class allowed them, so keep it as is.
+ // In String#replaceAll(), "\\\\" means single slash.
+ return text.replaceAll("\\\\;", ";")
+ .replaceAll("\\\\:", ":")
+ .replaceAll("\\\\,", ",")
+ .replaceAll("\\\\\\\\", "\\\\");
}
+
+ /**
+ * Parse the given stream and constructs VCardDataBuilder object.
+ * Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets
+ * local encoding to it. For example, Japanese phone career uses Shift_JIS, which
+ * is not formally allowed in vCard specification.
+ * As a result, there is a case where the encoding given here does not do well with
+ * the "CHARSET".
+ *
+ * In order to avoid such cases, It may be fine to use "ISO-8859-1" as an encoding,
+ * and to encode each localized String afterward.
+ *
+ * RFC 2426 "recommends" (not forces) to use UTF-8, so it may be OK to use
+ * UTF-8 as an encoding when parsing vCard 3.0. But note that some Japanese
+ * phone uses Shift_JIS as a charset (e.g. W61SH), and another uses
+ * "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification
+ * (e.g. W53K).
+ *
+ * @param is
+ * The source to parse.
+ * @param charset
+ * The charset.
+ * @param builder
+ * The v builder which used to construct data.
+ * @return Return true for success, otherwise false.
+ * @throws IOException
+ */
+ public boolean parse(InputStream is, String charset, VBuilder builder)
+ throws IOException, VCardException {
+ // TODO: If we really need to allow only CRLF as line break,
+ // we will have to develop our own BufferedReader().
+ mReader = new BufferedReader(new InputStreamReader(is, charset));
+
+ mBuilder = builder;
- /** groups "." word / word */
- private int parseGroups(int offset) {
- int ret = 0, sum = 0;
-
- ret = parseWord(offset);
- if (ret == PARSE_ERROR) {
- return PARSE_ERROR;
+ if (mBuilder != null) {
+ mBuilder.start();
}
- offset += ret;
- sum += ret;
-
- for (;;) {
- ret = parseString(offset, ".", false);
- if (ret == PARSE_ERROR) {
- break;
- }
-
- int ret1 = parseWord(offset);
- if (ret1 == PARSE_ERROR) {
- break;
- }
- offset += ret + ret1;
- sum += ret + ret1;
+ parseVCardFile();
+ if (mBuilder != null) {
+ mBuilder.end();
}
- return sum;
+ return true;
}
-
- /**
- * Translate escape characters("\\", "\;") which define in vcard2.1 spec.
- * But for fault tolerance, we will translate "\:" and "\,", which isn't
- * define in vcard2.1 explicitly, as the same behavior as other client.
- *
- * @param str:
- * the string will be translated.
- * @return the string which do not contain any escape character in vcard2.1
- */
- private String escapeTranslator(String str) {
- if (null == str)
- return null;
-
- String tmp = str.replace("\\\\", "\n\r\n");
- tmp = tmp.replace("\\;", ";");
- tmp = tmp.replace("\\:", ":");
- tmp = tmp.replace("\\,", ",");
- tmp = tmp.replace("\n\r\n", "\\");
- return tmp;
+
+ private boolean isLetter(char ch) {
+ if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+ return true;
+ }
+ return false;
}
}
diff --git a/core/java/android/syncml/pim/vcard/VCardParser_V30.java b/core/java/android/syncml/pim/vcard/VCardParser_V30.java
index c56cfed..901bd49 100644
--- a/core/java/android/syncml/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/syncml/pim/vcard/VCardParser_V30.java
@@ -16,142 +16,270 @@
package android.syncml.pim.vcard;
-import android.syncml.pim.VBuilder;
-
-import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.InputStream;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.HashSet;
/**
* This class is used to parse vcard3.0. <br>
- * It get useful data refer from android contact, and alter to vCard 2.1 format.
- * Then reuse vcard 2.1 parser to analyze the result.<br>
- * Please refer to vCard Specification 3.0
+ * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426)
*/
public class VCardParser_V30 extends VCardParser_V21 {
- private static final String V21LINEBREAKER = "\r\n";
-
private static final HashSet<String> acceptablePropsWithParam = new HashSet<String>(
- Arrays.asList("PHOTO", "LOGO", "TEL", "EMAIL", "ADR"));
-
- private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>(
- Arrays.asList("ORG", "NOTE", "TITLE", "FN", "N"));
-
- private static final HashMap<String, String> propV30ToV21Map = new HashMap<String, String>();
+ Arrays.asList(
+ "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.
+ private static final HashSet<String> sAcceptableEncodingV30 = new HashSet<String>(
+ Arrays.asList("7BIT", "8BIT", "BASE64", "B"));
+
+ // Although RFC 2426 specifies some property must not have parameters, we allow it,
+ // since there may be some careers which violates the RFC...
+ private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
- static {
- propV30ToV21Map.put("PHOTO", "PHOTO");
- propV30ToV21Map.put("LOGO", "PHOTO");
+ private String mPreviousLine;
+
+ @Override
+ protected String getVersion() {
+ return "3.0";
}
-
+
@Override
- public boolean parse(InputStream is, String encoding, VBuilder builder)
- throws IOException {
- // get useful info for android contact, and alter to vCard 2.1
- byte[] bytes = new byte[is.available()];
- is.read(bytes);
- String scStr = new String(bytes);
- StringBuilder v21str = new StringBuilder("");
-
- String[] strlist = splitProperty(scStr);
-
- if ("BEGIN:vCard".equals(strlist[0])
- || "BEGIN:VCARD".equals(strlist[0])) {
- v21str.append("BEGIN:VCARD" + V21LINEBREAKER);
+ protected boolean isValidPropertyName(String propertyName) {
+ return acceptablePropsWithParam.contains(propertyName) ||
+ acceptablePropsWithoutParam.contains(propertyName);
+ }
+
+ @Override
+ protected boolean isValidEncoding(String encoding) {
+ return sAcceptableEncodingV30.contains(encoding.toUpperCase());
+ }
+
+ @Override
+ protected String getLine() throws IOException {
+ if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
} else {
- return false;
+ return mReader.readLine();
}
-
- for (int i = 1; i < strlist.length - 1; i++) {// for ever property
- // line
- String propName;
- String params;
- String value;
-
- String line = strlist[i];
- if ("".equals(line)) { // line breaker is useful in encoding string
- v21str.append(V21LINEBREAKER);
- continue;
- }
-
- String[] contentline = line.split(":", 2);
- String propNameAndParam = contentline[0];
- value = (contentline.length > 1) ? contentline[1] : "";
- if (propNameAndParam.length() > 0) {
- String[] nameAndParams = propNameAndParam.split(";", 2);
- propName = nameAndParams[0];
- params = (nameAndParams.length > 1) ? nameAndParams[1] : "";
-
- if (acceptablePropsWithParam.contains(propName)
- || acceptablePropsWithoutParam.contains(propName)) {
- v21str.append(mapContentlineV30ToV21(propName, params,
- value));
+ }
+
+ /**
+ * vCard 3.0 requires that the line with space at the beginning of the line
+ * must be combined with previous line.
+ */
+ @Override
+ protected String getNonEmptyLine() throws IOException, VCardException {
+ String line;
+ StringBuilder builder = null;
+ while (true) {
+ line = mReader.readLine();
+ if (line == null) {
+ if (builder != null) {
+ return builder.toString();
+ } else if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ }
+ throw new VCardException("Reached end of buffer.");
+ } else if (line.length() == 0) {
+ if (builder != null) {
+ return builder.toString();
+ } else if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ }
+ } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
+ if (builder != null) {
+ // TODO: Check whether MIME requires only one whitespace.
+ builder.append(line.substring(1));
+ } else if (mPreviousLine != null) {
+ builder = new StringBuilder();
+ builder.append(mPreviousLine);
+ mPreviousLine = null;
+ builder.append(line.substring(1));
+ } else {
+ throw new VCardException("Space exists at the beginning of the line");
+ }
+ } else {
+ if (mPreviousLine == null) {
+ mPreviousLine = line;
+ } else {
+ String ret = mPreviousLine;
+ mPreviousLine = line;
+ return ret;
}
}
- }// end for
-
- if ("END:vCard".equals(strlist[strlist.length - 1])
- || "END:VCARD".equals(strlist[strlist.length - 1])) {
- v21str.append("END:VCARD" + V21LINEBREAKER);
- } else {
- return false;
}
-
- return super.parse(
- // use vCard 2.1 parser
- new ByteArrayInputStream(v21str.toString().getBytes()),
- encoding, builder);
}
-
+
+
/**
- * Convert V30 string to V21 string
- *
- * @param propName
- * The name of property
- * @param params
- * parameter of property
- * @param value
- * value of property
- * @return the converted string
+ * vcard = [group "."] "BEGIN" ":" "VCARD" 1*CRLF
+ * 1*(contentline)
+ * ;A vCard object MUST include the VERSION, FN and N types.
+ * [group "."] "END" ":" "VCARD" 1*CRLF
*/
- private String mapContentlineV30ToV21(String propName, String params,
- String value) {
- String result;
+ @Override
+ protected boolean readBeginVCard() throws IOException, VCardException {
+ // TODO: vCard 3.0 supports group.
+ return super.readBeginVCard();
+ }
+
+ @Override
+ protected void readEndVCard() throws VCardException {
+ // TODO: vCard 3.0 supports group.
+ super.readEndVCard();
+ }
- if (propV30ToV21Map.containsKey(propName)) {
- result = propV30ToV21Map.get(propName);
- } else {
- result = propName;
+ /**
+ * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
+ */
+ @Override
+ protected void handleParams(String params) throws VCardException {
+ try {
+ super.handleParams(params);
+ } catch (VCardException e) {
+ // maybe IANA type
+ String[] strArray = params.split("=", 2);
+ if (strArray.length == 2) {
+ handleAnyParam(strArray[0], strArray[1]);
+ } else {
+ // Must not come here in the current implementation.
+ throw new VCardException(
+ "Unknown params value: " + params);
+ }
+ }
+ }
+
+ @Override
+ protected void handleAnyParam(String paramName, String paramValue) {
+ // vCard 3.0 accept comma-separated multiple values, but
+ // current PropertyNode does not accept it.
+ // For now, we do not split the values.
+ //
+ // TODO: fix this.
+ super.handleAnyParam(paramName, paramValue);
+ }
+
+ /**
+ * vCard 3.0 defines
+ *
+ * param = param-name "=" param-value *("," param-value)
+ * param-name = iana-token / x-name
+ * param-value = ptext / quoted-string
+ * quoted-string = DQUOTE QSAFE-CHAR DQUOTE
+ */
+ @Override
+ protected void handleType(String ptypevalues) {
+ String[] ptypeArray = ptypevalues.split(",");
+ mBuilder.propertyParamType("TYPE");
+ for (String value : ptypeArray) {
+ int length = value.length();
+ if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
+ mBuilder.propertyParamValue(value.substring(1, value.length() - 1));
+ } else {
+ mBuilder.propertyParamValue(value);
+ }
}
- // Alter parameter part of property to vCard 2.1 format
- if (acceptablePropsWithParam.contains(propName) && params.length() > 0)
- result = result
- + ";"
- + params.replaceAll(",", ";").replaceAll("ENCODING=B",
- "ENCODING=BASE64").replaceAll("ENCODING=b",
- "ENCODING=BASE64");
+ }
- return result + ":" + value + V21LINEBREAKER;
+ @Override
+ protected void handleAgent(String propertyValue) throws VCardException {
+ // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.0.
+ //
+ // e.g.
+ // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
+ // TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
+ // ET:jfriday@host.com\nEND:VCARD\n
+ //
+ // TODO: fix this.
+ //
+ // issue:
+ // vCard 3.0 also allows this as an example.
+ //
+ // AGENT;VALUE=uri:
+ // CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
+ //
+ // This is not VCARD. Should we support this?
+ throw new VCardException("AGENT in vCard 3.0 is not supported yet.");
+ }
+
+ // vCard 3.0 supports "B" as BASE64 encoding.
+ @Override
+ protected void handlePropertyValue(
+ String propertyName, String propertyValue) throws
+ IOException, VCardException {
+ if (mEncoding != null && mEncoding.equalsIgnoreCase("B")) {
+ String result = getBase64(propertyValue);
+ if (mBuilder != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(result);
+ mBuilder.propertyValues(v);
+ }
+ }
+
+ super.handlePropertyValue(propertyName, propertyValue);
+ }
+
+ /**
+ * vCard 3.0 does not require two CRLF at the last of BASE64 data.
+ * It only requires that data should be MIME-encoded.
+ */
+ @Override
+ protected String getBase64(String firstString) throws IOException, VCardException {
+ StringBuilder builder = new StringBuilder();
+ builder.append(firstString);
+
+ while (true) {
+ String line = getLine();
+ if (line == null) {
+ throw new VCardException(
+ "File ended during parsing BASE64 binary");
+ }
+ if (line.length() == 0) {
+ break;
+ } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
+ mPreviousLine = line;
+ break;
+ }
+ builder.append(line);
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Return unescapeText(text).
+ * In vCard 3.0, 8bit text is always encoded.
+ */
+ @Override
+ protected String maybeUnescapeText(String text) {
+ return unescapeText(text);
}
/**
- * Split ever property line to Stringp[], not split folding line.
- *
- * @param scStr
- * the string to be splitted
- * @return a list of splitted string
+ * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
+ * ; \\ encodes \, \n or \N encodes newline
+ * ; \; encodes ;, \, encodes ,
*/
- private String[] splitProperty(String scStr) {
- /*
- * Property splitted by \n, and unfold folding lines by removing
- * CRLF+LWSP-char
- */
- scStr = scStr.replaceAll("\r\n", "\n").replaceAll("\n ", "")
- .replaceAll("\n\t", "");
- String[] strs = scStr.split("\n");
- return strs;
+ @Override
+ protected String unescapeText(String text) {
+ // In String#replaceAll(), "\\\\" means single slash.
+ return text.replaceAll("\\\\;", ";")
+ .replaceAll("\\\\:", ":")
+ .replaceAll("\\\\,", ",")
+ .replaceAll("\\\\n", "\r\n")
+ .replaceAll("\\\\N", "\r\n")
+ .replaceAll("\\\\\\\\", "\\\\");
}
}
diff --git a/core/java/android/syncml/pim/vcard/VCardVersionException.java b/core/java/android/syncml/pim/vcard/VCardVersionException.java
new file mode 100644
index 0000000..1ca88d1
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardVersionException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * 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 android.syncml.pim.vcard;
+
+/**
+ * VCardException used only when the version of the vCard is different.
+ */
+public class VCardVersionException extends VCardException {
+ public VCardVersionException() {
+ }
+
+ public VCardVersionException(String message) {
+ super(message);
+ }
+}
diff --git a/tests/AndroidTests/res/raw/v21_backslash.vcf b/tests/AndroidTests/res/raw/v21_backslash.vcf
new file mode 100644
index 0000000..bd3002b
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_backslash.vcf
@@ -0,0 +1,5 @@
+BEGIN:VCARD
+VERSION:2.1
+N:;A\;B\\;C\\\;;D;\:E;\\\\;
+FN:A;B\C\;D:E\\
+END:VCARD
diff --git a/tests/AndroidTests/res/raw/v21_complicated.vcf b/tests/AndroidTests/res/raw/v21_complicated.vcf
new file mode 100644
index 0000000..de34e16
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_complicated.vcf
@@ -0,0 +1,106 @@
+BEGIN:VCARD
+VERSION:2.1
+N:Gump;Forrest;Hoge;Pos;Tao
+FN:Joe Due
+ORG:Gump Shrimp Co.;Sales Dept.\;Manager;Fish keeper
+ROLE:Fish Cake Keeper!
+X-CLASS:PUBLIC
+TITLE:Shrimp Man
+TEL;WORK;VOICE:(111) 555-1212
+TEL;HOME;VOICE:(404) 555-1212
+TEL;CELL:0311111111
+TEL;VIDEO:0322222222
+TEL;VOICE:0333333333
+ADR;WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America
+LABEL;WORK;ENCODING=QUOTED-PRINTABLE:100 Waters Edge=0D=0ABaytown, LA 30314=0D=0AUnited States of America
+ADR;HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America
+LABEL;HOME;ENCODING=QUOTED-PRINTABLE:42 Plantation St.=0D=0A=
+Baytown, LA 30314=0D=0A=
+United States of America
+EMAIL;PREF;INTERNET:forrestgump@walladalla.com
+EMAIL;CELL:cell@example.com
+NOTE:The following note is the example from RFC 2045.
+NOTE;ENCODING=QUOTED-PRINTABLE:Now's the time =
+for all folk to come=
+ to the aid of their country.
+
+PHOTO;ENCODING=BASE64;TYPE=JPEG:
+ /9j/4QoPRXhpZgAATU0AKgAAAAgADQEOAAIAAAAPAAAAqgEPAAIAAAAHAAAAugEQAAIAAAAG
+ AAAAwgESAAMAAAABAAEAAAEaAAUAAAABAAAAyAEbAAUAAAABAAAA0AEoAAMAAAABAAIAAAEx
+ AAIAAAAOAAAA2AEyAAIAAAAUAAAA5gITAAMAAAABAAEAAIKYAAIAAAAOAAAA+odpAAQAAAAB
+ AAABhMSlAAcAAAB8AAABCAAABB4yMDA4MTAyOTEzNTUzMQAARG9Db01vAABEOTA1aQAAAABI
+ AAAAAQAAAEgAAAABRDkwNWkgVmVyMS4wMAAyMDA4OjEwOjI5IDEzOjU1OjQ3ACAgICAgICAg
+ ICAgICAAUHJpbnRJTQAwMzAwAAAABgABABQAFAACAQAAAAADAAAANAEABQAAAAEBAQAAAAEQ
+ gAAAAAAAEQkAACcQAAAPCwAAJxAAAAWXAAAnEAAACLAAACcQAAAcAQAAJxAAAAJeAAAnEAAA
+ AIsAACcQAAADywAAJxAAABvlAAAnEAAogpoABQAAAAEAAANqgp0ABQAAAAEAAANyiCIAAwAA
+ AAEAAgAAkAAABwAAAAQwMjIwkAMAAgAAABQAAAN6kAQAAgAAABQAAAOOkQEABwAAAAQBAgMA
+ kQIABQAAAAEAAAOikgEACgAAAAEAAAOqkgIABQAAAAEAAAOykgQACgAAAAEAAAO6kgUABQAA
+ AAEAAAPCkgcAAwAAAAEAAgAAkggAAwAAAAEAAAAAkgkAAwAAAAEAAAAAkgoABQAAAAEAAAPK
+ knwABwAAAAEAAAAAkoYABwAAABYAAAPSoAAABwAAAAQwMTAwoAEAAwAAAAEAAQAAoAIAAwAA
+ AAEAYAAAoAMAAwAAAAEASAAAoAUABAAAAAEAAAQAog4ABQAAAAEAAAPoog8ABQAAAAEAAAPw
+ ohAAAwAAAAEAAgAAohcAAwAAAAEAAgAAowAABwAAAAEDAAAAowEABwAAAAEBAAAApAEAAwAA
+ AAEAAAAApAIAAwAAAAEAAAAApAMAAwAAAAEAAAAApAQABQAAAAEAAAP4pAUAAwAAAAEAHQAA
+ pAYAAwAAAAEAAAAApAcAAwAAAAEAAAAApAgAAwAAAAEAAAAApAkAAwAAAAEAAAAApAoAAwAA
+ AAEAAAAApAwAAwAAAAEAAgAAAAAAAAAAAFMAACcQAAABXgAAAGQyMDA4OjEwOjI5IDEzOjU1
+ OjMxADIwMDg6MTA6MjkgMTM6NTU6NDcAAAApiAAAGwAAAAKyAAAAZAAAAV4AAABkAAAAAAAA
+ AGQAAAAlAAAACgAADpIAAAPoAAAAAAAAAAAyMDA4MTAyOTEzNTUzMQAAICoAAAAKAAAq4gAA
+ AAoAAAAAAAAAAQACAAEAAgAAAARSOTgAAAIABwAAAAQwMTAwAAAAAAAGAQMAAwAAAAEABgAA
+ ARoABQAAAAEAAARsARsABQAAAAEAAAR0ASgAAwAAAAEAAgAAAgEABAAAAAEAAAR8AgIABAAA
+ AAEAAAWLAAAAAAAAAEgAAAABAAAASAAAAAH/2P/bAIQAIBYYHBgUIBwaHCQiICYwUDQwLCww
+ YkZKOlB0Znp4cmZwboCQuJyAiK6KbnCg2qKuvsTO0M58muLy4MjwuMrOxgEiJCQwKjBeNDRe
+ xoRwhMbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbG
+ /8AAEQgAeACgAwEhAAIRAQMRAf/EAaIAAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKCxAA
+ AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkK
+ FhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWG
+ h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl
+ 5ufo6erx8vP09fb3+Pn6AQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgsRAAIBAgQEAwQH
+ BQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBka
+ JicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKT
+ lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz
+ 9PX29/j5+v/aAAwDAQACEQMRAD8AFFSqKkZIoqRVpgSKKeBTEOApwFADsUYpgIRSEUANIppF
+ ICNhUTCgCMio2FICJhULCgC0oqVaAJFFSqKBkgFOApiHCnCgB2KMUCENJQA0imEUDGMKiYUA
+ RtUbUgIWqJhQBZSpFoAlWpVoGPFPFMQ7tSK2ODQA4yKO9HmKe9FxAzDHFIOlAAaYaAGNUTUD
+ ImqNqQETVE1AE6VKKAJFNSqaAHg08GmANIFFQM5Y5qJMBuT60ZNQIcrkVYSQMKuLGKaaasQx
+ qiagZE1RtSAjaomoAkQ1KpoAlU1IpoAkU07OBTArO+5qkV12Y71lfUBmaKkCRSuznrTFba2a
+ oCwGyM0E1qIjY1GxoGRNUZNICNqiagByGplNAEimpFNMB4YDvSucpxSYEIU04KazsAu1qArU
+ WELtPpTSposBNETt5pxNaoCNjUbGgCNjUZoGRtUTUgFU1KpoAkBqQHigCFnO7rUqOdlZp6gA
+ c+tODn1pXAXzD60eYfWncQvmNSGQ07gOMhCVEJGz1ptgS5yKYxqwGE1GxoAiamGkMapqVTQB
+ Kpp+eKAICfmqWM/Kaz6gANOBqQFzRmmAuaTNACsfkqMHmm9wJs8U0mtRDGNRsaAI2phpDI1N
+ SqaAJFNSA8UCISfmqSM/Kaz6jAHmnA1ICg0uaAFzSZpgKx+SmDrTe4E2eKaTWoiMmmMaAIzT
+ DSGRKakU0ASKaeDTERseakjPyms+oxAacDUgOBpc0gFzSZpgOY/KKYv3qrqIlpprQBjGoyaA
+ GGmmkMgU1IppgPBqQGgQu0Gn4wvFKwEQpwNZDHZpc0ALmigRKBleaQKBWtgA001QDGqM0gGm
+ mGkMrqakBoAepp4NMRIDTwaAE2A008GokgHxjd1pzKFpW0uAg5NSBBTirgOpDWgDTTTQAw0w
+ 0gGGmmgZWBp4pASKaeDTEOBp4NADwajbrUyBEkXWnSUdAGr1qeiAMSkNWAhphoAaaYaQDDTT
+ SGVRTwaYDxTwaBDwaeDQA4GlK5oauIeo20pGaLaAKqgU6hKwBSGmAhphoAaaYaQxhpppDKgN
+ PFMB4p4oEPFOBpgPBp4NAhwpwoAWloAKSgBDTTQMYaYaQDTTTSGA/9n/2wCEAAoHBwgHBgoI
+ CAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9
+ PjsBCgsLDg0OHBAQHDsoIig7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7
+ Ozs7Ozs7Ozs7Ozs7O//AABEIAEgAYAMBIQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAA
+ AQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNC
+ scEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hp
+ anN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS
+ 09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI
+ CQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVi
+ ctEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4
+ eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY
+ 2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AJ7SLgcVr20I4rNFGvbQAAHFaEUX
+ SrQi5HCMdKk8oY6VSJYx4hjpVaWMelAFC4iGDxWPdR8mkxmRdxjBrEvI+tZjN20xtHNbNqAc
+ UIDXg6Cr0WKtCY8XKQOyzOB3FKNWsyceZ+lS6sY6NkjvPSdwImBHUmmy4q076oCjOODWPdgc
+ 0MpGPdAYNYl4o5rNjNKzkyorZtXxihAa1vIDip7m9Frb7/4jwKcnyxbEzN3ieJppZsyZ4U1H
+ urzZau4mWVlNrGk0UuWPVa1YroXEIkHfrXZh5W90RWncAHmsi6bJNdQ0ZNw3BrGuiMGs2Mks
+ puBzWzbzdOaEBeOpR2oUtkk9hTru7iuo4m8wgemKyqTi04sBsfkEf68j8KlUQZz9o/SuZRj3
+ JYriAji4/Sp7W6htbV2aXcu70ramoxle4gN7HcIXjbis+4k5NdaaauhmVcv1rHuW61DGiG1m
+ 6c1s20/TmgAv5vmj57VKk3+ixnPc1xVV70h9CVJuOtSrL71hFgxzScUkkn+iY/2q1i9xDrGT
+ 9y31pJ5Otd1L+GhMy7mTrWXO2SapjRn28vTmta3nxjmgGOvJd2w1Kkv+ipz/ABGuOoveYdCe
+ ObjrU6y5rlsA8ycUksn+ij/eNaw6iJLNsW59zTJn6816FP4EJmbO+Saz5m602UjIgk4HNadv
+ LwKaBl+MpIMOMipp490SCJeF7CoqQvF2JuRqWQ4YEGrSiQJuKnHrXByMpki73GFBNXIoh9n2
+ SrnnOK6MPTbd3sSwIVF2qMCqkzHmuy1lYRnTHrVGWpZaMKB+BWlbycYoQM0IZDxzV+GU8c1a
+ IYy5Y+dnHatAsfsAHfArmS1mPoh1gT8x9qtk1rQX7tCe5DIapzGtGBQm71SlqGWjnIH6Vowt
+ zmhAy/E3vV6F6tEMuxlWIyAfrVxCCAO1VZEEyYA4AApxNGwyJ+lVJRUsaKMw61SlFQzRAP/Z
+
+X-ATTRIBUTE:Some String
+BDAY:19800101
+GEO:35.6563854,139.6994233
+URL:http://www.example.com/
+REV:20080424T195243Z
+END:VCARD \ No newline at end of file
diff --git a/tests/AndroidTests/res/raw/v21_japanese_1.vcf b/tests/AndroidTests/res/raw/v21_japanese_1.vcf
new file mode 100644
index 0000000..d05e2ff
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_japanese_1.vcf
@@ -0,0 +1,6 @@
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh;;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ;;;;
+TEL;PREF;VOICE:0300000000
+END:VCARD
diff --git a/tests/AndroidTests/res/raw/v21_japanese_2.vcf b/tests/AndroidTests/res/raw/v21_japanese_2.vcf
new file mode 100644
index 0000000..fa54acb
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_japanese_2.vcf
@@ -0,0 +1,10 @@
+BEGIN:VCARD
+VERSION:2.1
+FN;CHARSET=SHIFT_JIS:ˆÀ“¡ ƒƒCƒh 1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡;ƒƒCƒh1;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³;Û²ÄÞ1;;;
+ADR;HOME;CHARSET=SHIFT_JIS;ENCODING=QUOTED-PRINTABLE:;=93=8C=8B=9E=93=73=
+=8F=61=92=4A=8B=E6=8D=F7=8B=75=92=AC26-1=83=5A=83=8B=83=8A=83=41=83=93=
+=83=5E=83=8F=81=5B6=8A=4B;;;;150-8512;
+NOTE;CHARSET=SHIFT_JIS;ENCODING=QUOTED-PRINTABLE:=83=81=83=82
+END:VCARD
diff --git a/tests/AndroidTests/res/raw/v21_multiple_entry.vcf b/tests/AndroidTests/res/raw/v21_multiple_entry.vcf
new file mode 100644
index 0000000..ebbb19a
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_multiple_entry.vcf
@@ -0,0 +1,33 @@
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh3;;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ3;;;;
+TEL;X-NEC-SECRET:9
+TEL;X-NEC-HOTEL:10
+TEL;X-NEC-SCHOOL:11
+TEL;HOME;FAX:12
+END:VCARD
+
+
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh4;;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ4;;;;
+TEL;MODEM:13
+TEL;PAGER:14
+TEL;X-NEC-FAMILY:15
+TEL;X-NEC-GIRL:16
+END:VCARD
+
+
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh5;;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ5;;;;
+TEL;X-NEC-BOY:17
+TEL;X-NEC-FRIEND:18
+TEL;X-NEC-PHS:19
+TEL;X-NEC-RESTAURANT:20
+END:VCARD
+
+
diff --git a/tests/AndroidTests/res/raw/v21_simple.vcf b/tests/AndroidTests/res/raw/v21_simple.vcf
new file mode 100644
index 0000000..beddabb
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_simple.vcf
@@ -0,0 +1,4 @@
+BEGIN:VCARD
+N:Ando;Roid;
+FN:Ando Roid
+END:VCARD
diff --git a/tests/AndroidTests/res/raw/v30_simple.vcf b/tests/AndroidTests/res/raw/v30_simple.vcf
new file mode 100644
index 0000000..418661f
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v30_simple.vcf
@@ -0,0 +1,13 @@
+BEGIN:VCARD
+VERSION:3.0
+FN:And Roid
+N:And;Roid;;;
+ORG:Open;Handset; Alliance
+SORT-STRING:android
+TEL;TYPE=PREF;TYPE=VOICE:0300000000
+CLASS:PUBLIC
+X-GNO:0
+X-GN:group0
+X-REDUCTION:0
+REV:20081031T065854Z
+END:VCARD
diff --git a/tests/AndroidTests/src/com/android/unit_tests/VCardTests.java b/tests/AndroidTests/src/com/android/unit_tests/VCardTests.java
new file mode 100644
index 0000000..b7f562d
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/VCardTests.java
@@ -0,0 +1,773 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * 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.android.unit_tests;
+
+import android.content.ContentValues;
+import android.syncml.pim.PropertyNode;
+import android.syncml.pim.VDataBuilder;
+import android.syncml.pim.VNode;
+import android.syncml.pim.vcard.VCardException;
+import android.syncml.pim.vcard.VCardParser_V21;
+import android.syncml.pim.vcard.VCardParser_V30;
+import android.test.AndroidTestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Vector;
+
+public class VCardTests extends AndroidTestCase {
+
+ private class PropertyNodesVerifier {
+ private HashMap<String, Vector<PropertyNode>> mPropertyNodeMap;
+ public PropertyNodesVerifier(PropertyNode... nodes) {
+ mPropertyNodeMap = new HashMap<String, Vector<PropertyNode>>();
+ for (PropertyNode propertyNode : nodes) {
+ String propName = propertyNode.propName;
+ Vector<PropertyNode> expectedNodes =
+ mPropertyNodeMap.get(propName);
+ if (expectedNodes == null) {
+ expectedNodes = new Vector<PropertyNode>();
+ mPropertyNodeMap.put(propName, expectedNodes);
+ }
+ expectedNodes.add(propertyNode);
+ }
+ }
+
+ public void verify(VNode vnode) {
+ for (PropertyNode propertyNode : vnode.propList) {
+ String propName = propertyNode.propName;
+ Vector<PropertyNode> nodes = mPropertyNodeMap.get(propName);
+ if (nodes == null) {
+ fail("Unexpected propName \"" + propName + "\" exists.");
+ }
+ boolean successful = false;
+ int size = nodes.size();
+ for (int i = 0; i < size; i++) {
+ PropertyNode expectedNode = nodes.get(i);
+ if (expectedNode.propName.equals(propName)) {
+ if (expectedNode.equals(propertyNode)) {
+ successful = true;
+ nodes.remove(i);
+ if (nodes.size() == 0) {
+ mPropertyNodeMap.remove(propName);
+ }
+ break;
+ } else {
+ fail("Property \"" + propName + "\" has wrong value.\n"
+ + "expected: " + expectedNode.toString()
+ + "\n actual: " + propertyNode.toString());
+ }
+ }
+ }
+ if (!successful) {
+ fail("Unexpected property \"" + propName + "\" exists.");
+ }
+ }
+ if (mPropertyNodeMap.size() != 0) {
+ Vector<String> expectedProps = new Vector<String>();
+ for (Vector<PropertyNode> nodes : mPropertyNodeMap.values()) {
+ for (PropertyNode node : nodes) {
+ expectedProps.add(node.propName);
+ }
+ }
+ fail("expected props " + Arrays.toString(expectedProps.toArray()) +
+ " was not found");
+ }
+ }
+ }
+
+ public void testV21SimpleCase() throws IOException, VCardException {
+ VCardParser_V21 parser = new VCardParser_V21();
+ VDataBuilder builder = new VDataBuilder();
+ InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple);
+ assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+ is.close();
+ assertEquals(1, builder.vNodeList.size());
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+ new PropertyNode("N", "Ando;Roid;",
+ Arrays.asList("Ando", "Roid", ""),
+ null, null, null, null),
+ new PropertyNode("FN", "Ando Roid",
+ null, null, null, null, null));
+ verifier.verify(builder.vNodeList.get(0));
+ }
+
+ public void testV21BackslashCase() throws IOException, VCardException {
+ VCardParser_V21 parser = new VCardParser_V21();
+ VDataBuilder builder = new VDataBuilder();
+ InputStream is = getContext().getResources().openRawResource(R.raw.v21_backslash);
+ assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+ is.close();
+ assertEquals(1, builder.vNodeList.size());
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+ new PropertyNode("VERSION", "2.1",
+ null, null, null, null, null),
+ new PropertyNode("N", ";A;B\\;C\\;;D;:E;\\\\;",
+ Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""),
+ null, null, null, null),
+ new PropertyNode("FN", "A;B\\C\\;D:E\\\\",
+ null, null, null, null, null));
+ verifier.verify(builder.vNodeList.get(0));
+ }
+
+ public void testV21ComplicatedCase() throws IOException, VCardException {
+ VCardParser_V21 parser = new VCardParser_V21();
+ VDataBuilder builder = new VDataBuilder();
+ InputStream is = getContext().getResources().openRawResource(R.raw.v21_complicated);
+ assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+ is.close();
+ assertEquals(1, builder.vNodeList.size());
+ ContentValues contentValuesForQP = new ContentValues();
+ contentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
+ ContentValues contentValuesForPhoto = new ContentValues();
+ contentValuesForPhoto.put("ENCODING", "BASE64");
+ // Push data into int array at first since values like 0x80 are
+ // interpreted as int by the compiler and casting all of them is
+ // cumbersome...
+ int[] photoIntArray = {
+ 0xff, 0xd8, 0xff, 0xe1, 0x0a, 0x0f, 0x45, 0x78, 0x69, 0x66, 0x00,
+ 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0d,
+ 0x01, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0xaa, 0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
+ 0x00, 0xba, 0x01, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00,
+ 0x00, 0x00, 0xc2, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xc8, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x28, 0x00, 0x03, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xd8, 0x01, 0x32, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xe6, 0x02, 0x13,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x82,
+ 0x98, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xfa,
+ 0x87, 0x69, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x84, 0xc4, 0xa5, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00,
+ 0x01, 0x08, 0x00, 0x00, 0x04, 0x1e, 0x32, 0x30, 0x30, 0x38, 0x31,
+ 0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31, 0x00, 0x00,
+ 0x44, 0x6f, 0x43, 0x6f, 0x4d, 0x6f, 0x00, 0x00, 0x44, 0x39, 0x30,
+ 0x35, 0x69, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x44, 0x39, 0x30,
+ 0x35, 0x69, 0x20, 0x56, 0x65, 0x72, 0x31, 0x2e, 0x30, 0x30, 0x00,
+ 0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
+ 0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x00, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x49, 0x4d, 0x00, 0x30, 0x33,
+ 0x30, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x14, 0x00,
+ 0x14, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x34, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
+ 0x00, 0x00, 0x00, 0x01, 0x10, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x11, 0x09, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x0f, 0x0b, 0x00,
+ 0x00, 0x27, 0x10, 0x00, 0x00, 0x05, 0x97, 0x00, 0x00, 0x27, 0x10,
+ 0x00, 0x00, 0x08, 0xb0, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1c,
+ 0x01, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x02, 0x5e, 0x00, 0x00,
+ 0x27, 0x10, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x27, 0x10, 0x00,
+ 0x00, 0x03, 0xcb, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1b, 0xe5,
+ 0x00, 0x00, 0x27, 0x10, 0x00, 0x28, 0x82, 0x9a, 0x00, 0x05, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x6a, 0x82, 0x9d, 0x00, 0x05,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x72, 0x88, 0x22, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x90, 0x00,
+ 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x32, 0x32, 0x30, 0x90,
+ 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03, 0x7a,
+ 0x90, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03,
+ 0x8e, 0x91, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02,
+ 0x03, 0x00, 0x91, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x03, 0xa2, 0x92, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x03, 0xaa, 0x92, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x03, 0xb2, 0x92, 0x04, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x03, 0xba, 0x92, 0x05, 0x00, 0x05, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xc2, 0x92, 0x07, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x92, 0x08, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92, 0x09,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92,
+ 0x0a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xca,
+ 0x92, 0x7c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x92, 0x86, 0x00, 0x07, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00,
+ 0x03, 0xd2, 0xa0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30,
+ 0x31, 0x30, 0x30, 0xa0, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x60, 0x00, 0x00, 0xa0, 0x03, 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x48, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x00, 0xa2, 0x0e, 0x00, 0x05,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xe8, 0xa2, 0x0f, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xf0, 0xa2, 0x10,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0xa2,
+ 0x17, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00,
+ 0xa3, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00,
+ 0x00, 0xa3, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0xa4, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x04, 0x00, 0x05, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x03, 0xf8, 0xa4, 0x05, 0x00, 0x03, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x1d, 0x00, 0x00, 0xa4, 0x06, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x07, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x08,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4,
+ 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0xa4, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0xa4, 0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00,
+ 0x00, 0x27, 0x10, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64,
+ 0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
+ 0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x33, 0x31, 0x00, 0x32, 0x30,
+ 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20, 0x31, 0x33,
+ 0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x00, 0x00, 0x29, 0x88,
+ 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x02, 0xb2, 0x00, 0x00, 0x00,
+ 0x64, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x25, 0x00,
+ 0x00, 0x00, 0x0a, 0x00, 0x00, 0x0e, 0x92, 0x00, 0x00, 0x03, 0xe8,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x30, 0x30,
+ 0x38, 0x31, 0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31,
+ 0x00, 0x00, 0x20, 0x2a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x2a,
+ 0xe2, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x04, 0x52, 0x39, 0x38, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00,
+ 0x00, 0x04, 0x30, 0x31, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x06, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06,
+ 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x04, 0x6c, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x04, 0x74, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x04, 0x7c, 0x02, 0x02, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x8b, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84,
+ 0x00, 0x20, 0x16, 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, 0x1a, 0x1c,
+ 0x24, 0x22, 0x20, 0x26, 0x30, 0x50, 0x34, 0x30, 0x2c, 0x2c, 0x30,
+ 0x62, 0x46, 0x4a, 0x3a, 0x50, 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66,
+ 0x70, 0x6e, 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, 0xae, 0x8a, 0x6e,
+ 0x70, 0xa0, 0xda, 0xa2, 0xae, 0xbe, 0xc4, 0xce, 0xd0, 0xce, 0x7c,
+ 0x9a, 0xe2, 0xf2, 0xe0, 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, 0x01,
+ 0x22, 0x24, 0x24, 0x30, 0x2a, 0x30, 0x5e, 0x34, 0x34, 0x5e, 0xc6,
+ 0x84, 0x70, 0x84, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xff, 0xc0,
+ 0x00, 0x11, 0x08, 0x00, 0x78, 0x00, 0xa0, 0x03, 0x01, 0x21, 0x00,
+ 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00,
+ 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03,
+ 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01,
+ 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31,
+ 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81,
+ 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
+ 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19,
+ 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+ 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
+ 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+ 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92,
+ 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
+ 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+ 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
+ 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+ 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
+ 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00,
+ 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04,
+ 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77,
+ 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12,
+ 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14,
+ 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
+ 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17,
+ 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+ 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
+ 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+ 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
+ 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
+ 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+ 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
+ 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2,
+ 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00,
+ 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00,
+ 0x14, 0x54, 0xaa, 0x2a, 0x46, 0x48, 0xa2, 0xa4, 0x55, 0xa6, 0x04,
+ 0x8a, 0x29, 0xe0, 0x53, 0x10, 0xe0, 0x29, 0xc0, 0x50, 0x03, 0xb1,
+ 0x46, 0x29, 0x80, 0x84, 0x52, 0x11, 0x40, 0x0d, 0x22, 0x9a, 0x45,
+ 0x20, 0x23, 0x61, 0x51, 0x30, 0xa0, 0x08, 0xc8, 0xa8, 0xd8, 0x52,
+ 0x02, 0x26, 0x15, 0x0b, 0x0a, 0x00, 0xb4, 0xa2, 0xa5, 0x5a, 0x00,
+ 0x91, 0x45, 0x4a, 0xa2, 0x81, 0x92, 0x01, 0x4e, 0x02, 0x98, 0x87,
+ 0x0a, 0x70, 0xa0, 0x07, 0x62, 0x8c, 0x50, 0x21, 0x0d, 0x25, 0x00,
+ 0x34, 0x8a, 0x61, 0x14, 0x0c, 0x63, 0x0a, 0x89, 0x85, 0x00, 0x46,
+ 0xd5, 0x1b, 0x52, 0x02, 0x16, 0xa8, 0x98, 0x50, 0x05, 0x94, 0xa9,
+ 0x16, 0x80, 0x25, 0x5a, 0x95, 0x68, 0x18, 0xf1, 0x4f, 0x14, 0xc4,
+ 0x3b, 0xb5, 0x22, 0xb6, 0x38, 0x34, 0x00, 0xe3, 0x22, 0x8e, 0xf4,
+ 0x79, 0x8a, 0x7b, 0xd1, 0x71, 0x03, 0x30, 0xc7, 0x14, 0x83, 0xa5,
+ 0x00, 0x06, 0x98, 0x68, 0x01, 0x8d, 0x51, 0x35, 0x03, 0x22, 0x6a,
+ 0x8d, 0xa9, 0x01, 0x13, 0x54, 0x4d, 0x40, 0x13, 0xa5, 0x4a, 0x28,
+ 0x02, 0x45, 0x35, 0x2a, 0x9a, 0x00, 0x78, 0x34, 0xf0, 0x69, 0x80,
+ 0x34, 0x81, 0x45, 0x40, 0xce, 0x58, 0xe6, 0xa2, 0x4c, 0x06, 0xe4,
+ 0xfa, 0xd1, 0x93, 0x50, 0x21, 0xca, 0xe4, 0x55, 0x84, 0x90, 0x30,
+ 0xab, 0x8b, 0x18, 0xa6, 0x9a, 0x6a, 0xc4, 0x31, 0xaa, 0x26, 0xa0,
+ 0x64, 0x4d, 0x51, 0xb5, 0x20, 0x23, 0x6a, 0x89, 0xa8, 0x02, 0x44,
+ 0x35, 0x2a, 0x9a, 0x00, 0x95, 0x4d, 0x48, 0xa6, 0x80, 0x24, 0x53,
+ 0x4e, 0xce, 0x05, 0x30, 0x2b, 0x3b, 0xee, 0x6a, 0x91, 0x5d, 0x76,
+ 0x63, 0xbd, 0x65, 0x7d, 0x40, 0x66, 0x68, 0xa9, 0x02, 0x45, 0x2b,
+ 0xb3, 0x9e, 0xb4, 0xc5, 0x6d, 0xad, 0x9a, 0xa0, 0x2c, 0x06, 0xc8,
+ 0xcd, 0x04, 0xd6, 0xa2, 0x23, 0x63, 0x51, 0xb1, 0xa0, 0x64, 0x4d,
+ 0x51, 0x93, 0x48, 0x08, 0xda, 0xa2, 0x6a, 0x00, 0x72, 0x1a, 0x99,
+ 0x4d, 0x00, 0x48, 0xa6, 0xa4, 0x53, 0x4c, 0x07, 0x86, 0x03, 0xbd,
+ 0x2b, 0x9c, 0xa7, 0x14, 0x98, 0x10, 0x85, 0x34, 0xe0, 0xa6, 0xb3,
+ 0xb0, 0x0b, 0xb5, 0xa8, 0x0a, 0xd4, 0x58, 0x42, 0xed, 0x3e, 0x94,
+ 0xd2, 0xa6, 0x8b, 0x01, 0x34, 0x44, 0xed, 0xe6, 0x9c, 0x4d, 0x6a,
+ 0x80, 0x8d, 0x8d, 0x46, 0xc6, 0x80, 0x23, 0x63, 0x51, 0x9a, 0x06,
+ 0x46, 0xd5, 0x13, 0x52, 0x01, 0x54, 0xd4, 0xaa, 0x68, 0x02, 0x40,
+ 0x6a, 0x40, 0x78, 0xa0, 0x08, 0x59, 0xce, 0xee, 0xb5, 0x2a, 0x39,
+ 0xd9, 0x59, 0xa7, 0xa8, 0x00, 0x73, 0xeb, 0x4e, 0x0e, 0x7d, 0x69,
+ 0x5c, 0x05, 0xf3, 0x0f, 0xad, 0x1e, 0x61, 0xf5, 0xa7, 0x71, 0x0b,
+ 0xe6, 0x35, 0x21, 0x90, 0xd3, 0xb8, 0x0e, 0x32, 0x10, 0x95, 0x10,
+ 0x91, 0xb3, 0xd6, 0x9b, 0x60, 0x4b, 0x9c, 0x8a, 0x63, 0x1a, 0xb0,
+ 0x18, 0x4d, 0x46, 0xc6, 0x80, 0x22, 0x6a, 0x61, 0xa4, 0x31, 0xaa,
+ 0x6a, 0x55, 0x34, 0x01, 0x2a, 0x9a, 0x7e, 0x78, 0xa0, 0x08, 0x09,
+ 0xf9, 0xaa, 0x58, 0xcf, 0xca, 0x6b, 0x3e, 0xa0, 0x00, 0xd3, 0x81,
+ 0xa9, 0x01, 0x73, 0x46, 0x69, 0x80, 0xb9, 0xa4, 0xcd, 0x00, 0x2b,
+ 0x1f, 0x92, 0xa3, 0x07, 0x9a, 0x6f, 0x70, 0x26, 0xcf, 0x14, 0xd2,
+ 0x6b, 0x51, 0x0c, 0x63, 0x51, 0xb1, 0xa0, 0x08, 0xda, 0x98, 0x69,
+ 0x0c, 0x8d, 0x4d, 0x4a, 0xa6, 0x80, 0x24, 0x53, 0x52, 0x03, 0xc5,
+ 0x02, 0x21, 0x27, 0xe6, 0xa9, 0x23, 0x3f, 0x29, 0xac, 0xfa, 0x8c,
+ 0x01, 0xe6, 0x9c, 0x0d, 0x48, 0x0a, 0x0d, 0x2e, 0x68, 0x01, 0x73,
+ 0x49, 0x9a, 0x60, 0x2b, 0x1f, 0x92, 0x98, 0x3a, 0xd3, 0x7b, 0x81,
+ 0x36, 0x78, 0xa6, 0x93, 0x5a, 0x88, 0x8c, 0x9a, 0x63, 0x1a, 0x00,
+ 0x8c, 0xd3, 0x0d, 0x21, 0x91, 0x29, 0xa9, 0x14, 0xd0, 0x04, 0x8a,
+ 0x69, 0xe0, 0xd3, 0x11, 0x1b, 0x1e, 0x6a, 0x48, 0xcf, 0xca, 0x6b,
+ 0x3e, 0xa3, 0x10, 0x1a, 0x70, 0x35, 0x20, 0x38, 0x1a, 0x5c, 0xd2,
+ 0x01, 0x73, 0x49, 0x9a, 0x60, 0x39, 0x8f, 0xca, 0x29, 0x8b, 0xf7,
+ 0xaa, 0xba, 0x88, 0x96, 0x9a, 0x6b, 0x40, 0x18, 0xc6, 0xa3, 0x26,
+ 0x80, 0x18, 0x69, 0xa6, 0x90, 0xc8, 0x14, 0xd4, 0x8a, 0x69, 0x80,
+ 0xf0, 0x6a, 0x40, 0x68, 0x10, 0xbb, 0x41, 0xa7, 0xe3, 0x0b, 0xc5,
+ 0x2b, 0x01, 0x10, 0xa7, 0x03, 0x59, 0x0c, 0x76, 0x69, 0x73, 0x40,
+ 0x0b, 0x9a, 0x28, 0x11, 0x28, 0x19, 0x5e, 0x69, 0x02, 0x81, 0x5a,
+ 0xd8, 0x00, 0xd3, 0x4d, 0x50, 0x0c, 0x6a, 0x8c, 0xd2, 0x01, 0xa6,
+ 0x98, 0x69, 0x0c, 0xae, 0xa6, 0xa4, 0x06, 0x80, 0x1e, 0xa6, 0x9e,
+ 0x0d, 0x31, 0x12, 0x03, 0x4f, 0x06, 0x80, 0x13, 0x60, 0x34, 0xd3,
+ 0xc1, 0xa8, 0x92, 0x01, 0xf1, 0x8d, 0xdd, 0x69, 0xcc, 0xa1, 0x69,
+ 0x5b, 0x4b, 0x80, 0x83, 0x93, 0x52, 0x04, 0x14, 0xe2, 0xae, 0x03,
+ 0xa9, 0x0d, 0x68, 0x03, 0x4d, 0x34, 0xd0, 0x03, 0x0d, 0x30, 0xd2,
+ 0x01, 0x86, 0x9a, 0x68, 0x19, 0x58, 0x1a, 0x78, 0xa4, 0x04, 0x8a,
+ 0x69, 0xe0, 0xd3, 0x10, 0xe0, 0x69, 0xe0, 0xd0, 0x03, 0xc1, 0xa8,
+ 0xdb, 0xad, 0x4c, 0x81, 0x12, 0x45, 0xd6, 0x9d, 0x25, 0x1d, 0x00,
+ 0x6a, 0xf5, 0xa9, 0xe8, 0x80, 0x31, 0x29, 0x0d, 0x58, 0x08, 0x69,
+ 0x86, 0x80, 0x1a, 0x69, 0x86, 0x90, 0x0c, 0x34, 0xd3, 0x48, 0x65,
+ 0x51, 0x4f, 0x06, 0x98, 0x0f, 0x14, 0xf0, 0x68, 0x10, 0xf0, 0x69,
+ 0xe0, 0xd0, 0x03, 0x81, 0xa5, 0x2b, 0x9a, 0x1a, 0xb8, 0x87, 0xa8,
+ 0xdb, 0x4a, 0x46, 0x68, 0xb6, 0x80, 0x2a, 0xa8, 0x14, 0xea, 0x12,
+ 0xb0, 0x05, 0x21, 0xa6, 0x02, 0x1a, 0x61, 0xa0, 0x06, 0x9a, 0x61,
+ 0xa4, 0x31, 0x86, 0x9a, 0x69, 0x0c, 0xa8, 0x0d, 0x3c, 0x53, 0x01,
+ 0xe2, 0x9e, 0x28, 0x10, 0xf1, 0x4e, 0x06, 0x98, 0x0f, 0x06, 0x9e,
+ 0x0d, 0x02, 0x1c, 0x29, 0xc2, 0x80, 0x16, 0x96, 0x80, 0x0a, 0x4a,
+ 0x00, 0x43, 0x4d, 0x34, 0x0c, 0x61, 0xa6, 0x1a, 0x40, 0x34, 0xd3,
+ 0x4d, 0x21, 0x80, 0xff, 0xd9, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0a,
+ 0x07, 0x07, 0x08, 0x07, 0x06, 0x0a, 0x08, 0x08, 0x08, 0x0b, 0x0a,
+ 0x0a, 0x0b, 0x0e, 0x18, 0x10, 0x0e, 0x0d, 0x0d, 0x0e, 0x1d, 0x15,
+ 0x16, 0x11, 0x18, 0x23, 0x1f, 0x25, 0x24, 0x22, 0x1f, 0x22, 0x21,
+ 0x26, 0x2b, 0x37, 0x2f, 0x26, 0x29, 0x34, 0x29, 0x21, 0x22, 0x30,
+ 0x41, 0x31, 0x34, 0x39, 0x3b, 0x3e, 0x3e, 0x3e, 0x25, 0x2e, 0x44,
+ 0x49, 0x43, 0x3c, 0x48, 0x37, 0x3d, 0x3e, 0x3b, 0x01, 0x0a, 0x0b,
+ 0x0b, 0x0e, 0x0d, 0x0e, 0x1c, 0x10, 0x10, 0x1c, 0x3b, 0x28, 0x22,
+ 0x28, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0xff, 0xc0, 0x00, 0x11,
+ 0x08, 0x00, 0x48, 0x00, 0x60, 0x03, 0x01, 0x21, 0x00, 0x02, 0x11,
+ 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01,
+ 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02,
+ 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,
+ 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
+ 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1,
+ 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33,
+ 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25,
+ 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
+ 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+ 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
+ 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
+ 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8,
+ 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
+ 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
+ 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3,
+ 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00, 0x03, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03,
+ 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01,
+ 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
+ 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
+ 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72,
+ 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19,
+ 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
+ 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+ 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93,
+ 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
+ 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
+ 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,
+ 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4,
+ 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03,
+ 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0x9e, 0xd2,
+ 0x2e, 0x07, 0x15, 0xaf, 0x6d, 0x08, 0xe2, 0xb3, 0x45, 0x1a, 0xf6,
+ 0xd0, 0x00, 0x01, 0xc5, 0x68, 0x45, 0x17, 0x4a, 0xb4, 0x22, 0xe4,
+ 0x70, 0x8c, 0x74, 0xa9, 0x3c, 0xa1, 0x8e, 0x95, 0x48, 0x96, 0x31,
+ 0xe2, 0x18, 0xe9, 0x55, 0xa5, 0x8c, 0x7a, 0x50, 0x05, 0x0b, 0x88,
+ 0x86, 0x0f, 0x15, 0x8f, 0x75, 0x1f, 0x26, 0x93, 0x19, 0x91, 0x77,
+ 0x18, 0xc1, 0xac, 0x4b, 0xc8, 0xfa, 0xd6, 0x63, 0x37, 0x6d, 0x31,
+ 0xb4, 0x73, 0x5b, 0x36, 0xa0, 0x1c, 0x50, 0x80, 0xd7, 0x83, 0xa0,
+ 0xab, 0xd1, 0x62, 0xad, 0x09, 0x8f, 0x17, 0x29, 0x03, 0xb2, 0xcc,
+ 0xe0, 0x77, 0x14, 0xa3, 0x56, 0xb3, 0x27, 0x1e, 0x67, 0xe9, 0x52,
+ 0xea, 0xc6, 0x3a, 0x36, 0x48, 0xef, 0x3d, 0x27, 0x70, 0x22, 0x60,
+ 0x47, 0x52, 0x69, 0xb2, 0xe2, 0xad, 0x3b, 0xea, 0x80, 0xa3, 0x38,
+ 0xe0, 0xd6, 0x3d, 0xd8, 0x1c, 0xd0, 0xca, 0x46, 0x3d, 0xd0, 0x18,
+ 0x35, 0x89, 0x78, 0xa3, 0x9a, 0xcd, 0x8c, 0xd2, 0xb3, 0x93, 0x2a,
+ 0x2b, 0x66, 0xd5, 0xf1, 0x8a, 0x10, 0x1a, 0xd6, 0xf2, 0x03, 0x8a,
+ 0x9e, 0xe6, 0xf4, 0x5a, 0xdb, 0xef, 0xfe, 0x23, 0xc0, 0xa7, 0x27,
+ 0xcb, 0x16, 0xc4, 0xcc, 0xdd, 0xe2, 0x78, 0x9a, 0x69, 0x66, 0xcc,
+ 0x99, 0xe1, 0x4d, 0x47, 0xba, 0xbc, 0xd9, 0x6a, 0xee, 0x26, 0x59,
+ 0x59, 0x4d, 0xac, 0x69, 0x34, 0x52, 0xe5, 0x8f, 0x55, 0xad, 0x58,
+ 0xae, 0x85, 0xc4, 0x22, 0x41, 0xdf, 0xad, 0x76, 0x61, 0xe5, 0x6f,
+ 0x74, 0x45, 0x69, 0xdc, 0x00, 0x79, 0xac, 0x8b, 0xa6, 0xc9, 0x35,
+ 0xd4, 0x34, 0x64, 0xdc, 0x37, 0x06, 0xb1, 0xae, 0x88, 0xc1, 0xac,
+ 0xd8, 0xc9, 0x2c, 0xa6, 0xe0, 0x73, 0x5b, 0x36, 0xf3, 0x74, 0xe6,
+ 0x84, 0x05, 0xe3, 0xa9, 0x47, 0x6a, 0x14, 0xb6, 0x49, 0x3d, 0x85,
+ 0x3a, 0xee, 0xee, 0x2b, 0xa8, 0xe2, 0x6f, 0x30, 0x81, 0xe9, 0x8a,
+ 0xca, 0xa4, 0xe2, 0xd3, 0x8b, 0x01, 0xb1, 0xf9, 0x04, 0x7f, 0xaf,
+ 0x23, 0xf0, 0xa9, 0x54, 0x41, 0x9c, 0xfd, 0xa3, 0xf4, 0xae, 0x65,
+ 0x18, 0xf7, 0x25, 0x8a, 0xe2, 0x02, 0x38, 0xb8, 0xfd, 0x2a, 0x7b,
+ 0x5b, 0xa8, 0x6d, 0x6d, 0x5d, 0x9a, 0x5d, 0xcb, 0xbb, 0xd2, 0xb6,
+ 0xa6, 0xa3, 0x19, 0x5e, 0xe2, 0x03, 0x7b, 0x1d, 0xc2, 0x17, 0x8d,
+ 0xb8, 0xac, 0xfb, 0x89, 0x39, 0x35, 0xd6, 0x9a, 0x6a, 0xe8, 0x66,
+ 0x55, 0xcb, 0xf5, 0xac, 0x7b, 0x96, 0xeb, 0x50, 0xc6, 0x88, 0x6d,
+ 0x66, 0xe9, 0xcd, 0x6c, 0xdb, 0x4f, 0xd3, 0x9a, 0x00, 0x2f, 0xe6,
+ 0xf9, 0xa3, 0xe7, 0xb5, 0x4a, 0x93, 0x7f, 0xa2, 0xc6, 0x73, 0xdc,
+ 0xd7, 0x15, 0x55, 0xef, 0x48, 0x7d, 0x09, 0x52, 0x6e, 0x3a, 0xd4,
+ 0xab, 0x2f, 0xbd, 0x61, 0x16, 0x0c, 0x73, 0x49, 0xc5, 0x24, 0x92,
+ 0x7f, 0xa2, 0x63, 0xfd, 0xaa, 0xd6, 0x2f, 0x71, 0x0e, 0xb1, 0x93,
+ 0xf7, 0x2d, 0xf5, 0xa4, 0x9e, 0x4e, 0xb5, 0xdd, 0x4b, 0xf8, 0x68,
+ 0x4c, 0xcb, 0xb9, 0x93, 0xad, 0x65, 0xce, 0xd9, 0x26, 0xa9, 0x8d,
+ 0x19, 0xf6, 0xf2, 0xf4, 0xe6, 0xb5, 0xad, 0xe7, 0xc6, 0x39, 0xa0,
+ 0x18, 0xeb, 0xc9, 0x77, 0x6c, 0x35, 0x2a, 0x4b, 0xfe, 0x8a, 0x9c,
+ 0xff, 0x00, 0x11, 0xae, 0x3a, 0x8b, 0xde, 0x61, 0xd0, 0x9e, 0x39,
+ 0xb8, 0xeb, 0x53, 0xac, 0xb9, 0xae, 0x5b, 0x00, 0xf3, 0x27, 0x14,
+ 0x92, 0xc9, 0xfe, 0x8a, 0x3f, 0xde, 0x35, 0xac, 0x3a, 0x88, 0x92,
+ 0xcd, 0xb1, 0x6e, 0x7d, 0xcd, 0x32, 0x67, 0xeb, 0xcd, 0x7a, 0x14,
+ 0xfe, 0x04, 0x26, 0x66, 0xce, 0xf9, 0x26, 0xb3, 0xe6, 0x6e, 0xb4,
+ 0xd9, 0x48, 0xc8, 0x82, 0x4e, 0x07, 0x35, 0xa7, 0x6f, 0x2f, 0x02,
+ 0x9a, 0x06, 0x5f, 0x8c, 0xa4, 0x83, 0x0e, 0x32, 0x2a, 0x69, 0xe3,
+ 0xdd, 0x12, 0x08, 0x97, 0x85, 0xec, 0x2a, 0x2a, 0x42, 0xf1, 0x76,
+ 0x26, 0xe4, 0x6a, 0x59, 0x0e, 0x18, 0x10, 0x6a, 0xd2, 0x89, 0x02,
+ 0x6e, 0x2a, 0x71, 0xeb, 0x5c, 0x1c, 0x8c, 0xa6, 0x48, 0xbb, 0xdc,
+ 0x61, 0x41, 0x35, 0x72, 0x28, 0x87, 0xd9, 0xf6, 0x4a, 0xb9, 0xe7,
+ 0x38, 0xae, 0x8c, 0x3d, 0x36, 0xdd, 0xde, 0xc4, 0xb0, 0x21, 0x51,
+ 0x76, 0xa8, 0xc0, 0xaa, 0x93, 0x31, 0xe6, 0xbb, 0x2d, 0x65, 0x61,
+ 0x19, 0xd3, 0x1e, 0xb5, 0x46, 0x5a, 0x96, 0x5a, 0x30, 0xa0, 0x7e,
+ 0x05, 0x69, 0x5b, 0xc9, 0xc6, 0x28, 0x40, 0xcd, 0x08, 0x64, 0x3c,
+ 0x73, 0x57, 0xe1, 0x94, 0xf1, 0xcd, 0x5a, 0x21, 0x8c, 0xb9, 0x63,
+ 0xe7, 0x67, 0x1d, 0xab, 0x40, 0xb1, 0xfb, 0x00, 0x1d, 0xf0, 0x2b,
+ 0x99, 0x2d, 0x66, 0x3e, 0x88, 0x75, 0x81, 0x3f, 0x31, 0xf6, 0xab,
+ 0x64, 0xd6, 0xb4, 0x17, 0xee, 0xd0, 0x9e, 0xe4, 0x32, 0x1a, 0xa7,
+ 0x31, 0xad, 0x18, 0x14, 0x26, 0xef, 0x54, 0xa5, 0xa8, 0x65, 0xa3,
+ 0x9c, 0x81, 0xfa, 0x56, 0x8c, 0x2d, 0xce, 0x68, 0x40, 0xcb, 0xf1,
+ 0x37, 0xbd, 0x5e, 0x85, 0xea, 0xd1, 0x0c, 0xbb, 0x19, 0x56, 0x23,
+ 0x20, 0x1f, 0xad, 0x5c, 0x42, 0x08, 0x03, 0xb5, 0x55, 0x91, 0x04,
+ 0xc9, 0x80, 0x38, 0x00, 0x0a, 0x71, 0x34, 0x6c, 0x32, 0x27, 0xe9,
+ 0x55, 0x25, 0x15, 0x2c, 0x68, 0xa3, 0x30, 0xeb, 0x54, 0xa5, 0x15,
+ 0x0c, 0xd1, 0x00, 0xff, 0xd9};
+ int length = photoIntArray.length;
+ byte[] photoByteArray = new byte[length];
+ for (int i = 0; i < length; i++) {
+ photoByteArray[i] = (byte)photoIntArray[i];
+ }
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+ new PropertyNode("VERSION", "2.1",
+ null, null, null, null, null),
+ new PropertyNode("N", "Gump;Forrest;Hoge;Pos;Tao",
+ Arrays.asList("Gump", "Forrest",
+ "Hoge", "Pos", "Tao"),
+ null, null, null, null),
+ new PropertyNode("FN", "Joe Due",
+ null, null, null, null, null),
+ new PropertyNode("ORG",
+ "Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper",
+ Arrays.asList("Gump Shrimp Co.",
+ "Sales Dept.;Manager",
+ "Fish keeper"),
+ null, null, null, null),
+ new PropertyNode("ROLE", "Fish Cake Keeper!",
+ null, null, null, null, null),
+ new PropertyNode("TITLE", "Shrimp Man",
+ null, null, null, null, null),
+ new PropertyNode("X-CLASS", "PUBLIC",
+ null, null, null, null, null),
+ new PropertyNode("TEL", "(111) 555-1212",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("WORK", "VOICE")), null),
+ new PropertyNode("TEL", "(404) 555-1212",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("HOME", "VOICE")), null),
+ new PropertyNode("TEL", "0311111111",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("CELL")), null),
+ new PropertyNode("TEL", "0322222222",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("VIDEO")), null),
+ new PropertyNode("TEL", "0333333333",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("VOICE")), null),
+ new PropertyNode("ADR",
+ ";;100 Waters Edge;Baytown;LA;30314;United States of America",
+ Arrays.asList("", "", "100 Waters Edge", "Baytown",
+ "LA", "30314", "United States of America"),
+ null, null,
+ new HashSet<String>(Arrays.asList("WORK")), null),
+ new PropertyNode("LABEL",
+ "100 Waters Edge\r\nBaytown, LA 30314\r\nUnited States of America",
+ null, null, contentValuesForQP,
+ new HashSet<String>(Arrays.asList("WORK")), null),
+ new PropertyNode("ADR",
+ ";;42 Plantation St.;Baytown;LA;30314;United States of America",
+ Arrays.asList("", "", "42 Plantation St.", "Baytown",
+ "LA", "30314", "United States of America"), null, null,
+ new HashSet<String>(Arrays.asList("HOME")), null),
+ new PropertyNode("LABEL",
+ "42 Plantation St.\r\nBaytown, LA 30314\r\nUnited States of America",
+ null, null, contentValuesForQP,
+ new HashSet<String>(Arrays.asList("HOME")), null),
+ new PropertyNode("EMAIL", "forrestgump@walladalla.com",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("PREF", "INTERNET")), null),
+ new PropertyNode("EMAIL", "cell@example.com",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("CELL")), null),
+ new PropertyNode("NOTE", "The following note is the example from RFC 2045.",
+ null, null, null, null, null),
+ new PropertyNode("NOTE",
+ "Now's the time for all folk to come to the aid of their country.",
+ null, null, contentValuesForQP, null, null),
+ new PropertyNode("PHOTO", null,
+ null, photoByteArray, contentValuesForPhoto,
+ new HashSet<String>(Arrays.asList("JPEG")), null),
+ new PropertyNode("X-ATTRIBUTE", "Some String",
+ null, null, null, null, null),
+ new PropertyNode("BDAY", "19800101",
+ null, null, null, null, null),
+ new PropertyNode("GEO", "35.6563854,139.6994233",
+ null, null, null, null, null),
+ new PropertyNode("URL", "http://www.example.com/",
+ null, null, null, null, null),
+ new PropertyNode("REV", "20080424T195243Z",
+ null, null, null, null, null));
+ verifier.verify(builder.vNodeList.get(0));
+ }
+
+ public void testV21Japanese1() throws IOException, VCardException {
+ VCardParser_V21 parser = new VCardParser_V21();
+ VDataBuilder builder = new VDataBuilder();
+ InputStream is = getContext().getResources().openRawResource(R.raw.v21_japanese_1);
+ assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+ is.close();
+ assertEquals(1, builder.vNodeList.size());
+ ContentValues contentValuesForShiftJis = new ContentValues();
+ contentValuesForShiftJis.put("CHARSET", "SHIFT_JIS");
+ ContentValues contentValuesForQP = new ContentValues();
+ contentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
+ contentValuesForQP.put("CHARSET", "SHIFT_JIS");
+ // Though Japanese careers append ";;;;" at the end of the value of "SOUND",
+ // vCard 2.1/3.0 specification does not allow multiple values.
+ // Do not need to handle it as multiple values.
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+ new PropertyNode("VERSION", "2.1",
+ null, null, null, null, null),
+ new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""),
+ null, contentValuesForShiftJis, null, null),
+ new PropertyNode("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E;;;;",
+ null, null, contentValuesForShiftJis,
+ new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
+ new PropertyNode("TEL", "0300000000",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("VOICE", "PREF")), null));
+ verifier.verify(builder.vNodeList.get(0));
+ }
+
+ public void testV21Japanese2() throws IOException, VCardException {
+ VCardParser_V21 parser = new VCardParser_V21();
+ VDataBuilder builder = new VDataBuilder();
+ InputStream is = getContext().getResources().openRawResource(R.raw.v21_japanese_2);
+ assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+ is.close();
+ assertEquals(1, builder.vNodeList.size());
+ ContentValues contentValuesForShiftJis = new ContentValues();
+ contentValuesForShiftJis.put("CHARSET", "SHIFT_JIS");
+ ContentValues contentValuesForQP = new ContentValues();
+ contentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
+ contentValuesForQP.put("CHARSET", "SHIFT_JIS");
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+ new PropertyNode("VERSION", "2.1",
+ null, null, null, null, null),
+ new PropertyNode("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
+ Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031",
+ "", "", ""),
+ null, contentValuesForShiftJis, null, null),
+ new PropertyNode("FN",
+ "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031",
+ null, null, contentValuesForShiftJis, null, null),
+ new PropertyNode("SOUND",
+ ("\uFF71\uFF9D\uFF84\uFF9E\uFF73" +
+ ";\uFF9B\uFF72\uFF84\uFF9E\u0031;;;"),
+ null, null, contentValuesForShiftJis,
+ new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
+ new PropertyNode("ADR",
+ (";\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+ "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+ "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC\u0036" +
+ "\u968E;;;;150-8512;"),
+ Arrays.asList("",
+ "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+ "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+ "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
+ "\u0036\u968E", "", "", "", "150-8512", ""),
+ null, contentValuesForQP,
+ new HashSet<String>(Arrays.asList("HOME")), null),
+ new PropertyNode("NOTE", "\u30E1\u30E2",
+ null, null, contentValuesForQP, null, null));
+ verifier.verify(builder.vNodeList.get(0));
+ }
+
+ public void testV21MultipleEntryCase() throws IOException, VCardException {
+ VCardParser_V21 parser = new VCardParser_V21();
+ VDataBuilder builder = new VDataBuilder();
+ InputStream is = getContext().getResources().openRawResource(R.raw.v21_multiple_entry);
+ assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+ is.close();
+ assertEquals(3, builder.vNodeList.size());
+ ContentValues contentValuesForShiftJis = new ContentValues();
+ contentValuesForShiftJis.put("CHARSET", "SHIFT_JIS");
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+ new PropertyNode("VERSION", "2.1",
+ null, null, null, null, null),
+ new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""),
+ null, contentValuesForShiftJis, null, null),
+ new PropertyNode("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033;;;;",
+ null, null, contentValuesForShiftJis,
+ new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
+ new PropertyNode("TEL", "9",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("X-NEC-SECRET")), null),
+ new PropertyNode("TEL", "10",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("X-NEC-HOTEL")), null),
+ new PropertyNode("TEL", "11",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("X-NEC-SCHOOL")), null),
+ new PropertyNode("TEL", "12",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("FAX", "HOME")), null));
+ verifier.verify(builder.vNodeList.get(0));
+
+ verifier = new PropertyNodesVerifier(
+ new PropertyNode("VERSION", "2.1",
+ null, null, null, null, null),
+ new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""),
+ null, contentValuesForShiftJis, null, null),
+ new PropertyNode("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034;;;;",
+ null, null, contentValuesForShiftJis,
+ new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
+ new PropertyNode("TEL", "13",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("MODEM")), null),
+ new PropertyNode("TEL", "14",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("PAGER")), null),
+ new PropertyNode("TEL", "15",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("X-NEC-FAMILY")), null),
+ new PropertyNode("TEL", "16",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("X-NEC-GIRL")), null));
+ verifier.verify(builder.vNodeList.get(1));
+ verifier = new PropertyNodesVerifier(
+ new PropertyNode("VERSION", "2.1",
+ null, null, null, null, null),
+ new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""),
+ null, contentValuesForShiftJis, null, null),
+ new PropertyNode("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035;;;;",
+ null, null, contentValuesForShiftJis,
+ new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
+ new PropertyNode("TEL", "17",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("X-NEC-BOY")), null),
+ new PropertyNode("TEL", "18",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("X-NEC-FRIEND")), null),
+ new PropertyNode("TEL", "19",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("X-NEC-PHS")), null),
+ new PropertyNode("TEL", "20",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("X-NEC-RESTAURANT")), null));
+ verifier.verify(builder.vNodeList.get(2));
+ }
+
+ public void testV30SimpleCase() throws IOException, VCardException {
+ VCardParser_V21 parser = new VCardParser_V30();
+ VDataBuilder builder = new VDataBuilder();
+ InputStream is = getContext().getResources().openRawResource(R.raw.v30_simple);
+ assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+ is.close();
+ assertEquals(1, builder.vNodeList.size());
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+ new PropertyNode("VERSION", "3.0",
+ null, null, null, null, null),
+ new PropertyNode("FN", "And Roid",
+ null, null, null, null, null),
+ new PropertyNode("N", "And;Roid;;;",
+ Arrays.asList("And", "Roid", "", "", ""),
+ null, null, null, null),
+ new PropertyNode("ORG", "Open;Handset; Alliance",
+ Arrays.asList("Open", "Handset", " Alliance"),
+ null, null, null, null),
+ new PropertyNode("SORT-STRING", "android", null, null, null, null, null),
+ new PropertyNode("TEL", "0300000000",
+ null, null, null,
+ new HashSet<String>(Arrays.asList("PREF", "VOICE")), null),
+ new PropertyNode("CLASS", "PUBLIC", null, null, null, null, null),
+ new PropertyNode("X-GNO", "0", null, null, null, null, null),
+ new PropertyNode("X-GN", "group0", null, null, null, null, null),
+ new PropertyNode("X-REDUCTION", "0",
+ null, null, null, null, null),
+ new PropertyNode("REV", "20081031T065854Z",
+ null, null, null, null, null));
+ verifier.verify(builder.vNodeList.get(0));
+ }
+}