diff options
author | Daisuke Miyakawa <> | 2009-03-24 22:52:27 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-24 22:52:27 -0700 |
commit | 52fc2180fa535d12838498e877b6d7557c4024dd (patch) | |
tree | 6954b6a5606712b9359db15f2b298aa605ba8d74 | |
parent | 8c0d68f6695f4a0c98c12314eea81afcd59ea52d (diff) | |
download | frameworks_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.java | 137 | ||||
-rw-r--r-- | core/java/android/syncml/pim/VBuilder.java | 13 | ||||
-rw-r--r-- | core/java/android/syncml/pim/VDataBuilder.java | 220 | ||||
-rw-r--r-- | core/java/android/syncml/pim/vcard/VCardParser.java | 3 | ||||
-rw-r--r-- | core/java/android/syncml/pim/vcard/VCardParser_V21.java | 1379 | ||||
-rw-r--r-- | core/java/android/syncml/pim/vcard/VCardParser_V30.java | 348 | ||||
-rw-r--r-- | core/java/android/syncml/pim/vcard/VCardVersionException.java | 29 | ||||
-rw-r--r-- | tests/AndroidTests/res/raw/v21_backslash.vcf | 5 | ||||
-rw-r--r-- | tests/AndroidTests/res/raw/v21_complicated.vcf | 106 | ||||
-rw-r--r-- | tests/AndroidTests/res/raw/v21_japanese_1.vcf | 6 | ||||
-rw-r--r-- | tests/AndroidTests/res/raw/v21_japanese_2.vcf | 10 | ||||
-rw-r--r-- | tests/AndroidTests/res/raw/v21_multiple_entry.vcf | 33 | ||||
-rw-r--r-- | tests/AndroidTests/res/raw/v21_simple.vcf | 4 | ||||
-rw-r--r-- | tests/AndroidTests/res/raw/v30_simple.vcf | 13 | ||||
-rw-r--r-- | tests/AndroidTests/src/com/android/unit_tests/VCardTests.java | 773 |
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)); + } +} |