summaryrefslogtreecommitdiffstats
path: root/core/java/android/syncml
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
commit54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch)
tree35051494d2af230dce54d6b31c6af8fc24091316 /core/java/android/syncml
downloadframeworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip
frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz
frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2
Initial Contribution
Diffstat (limited to 'core/java/android/syncml')
-rw-r--r--core/java/android/syncml/package.html6
-rw-r--r--core/java/android/syncml/pim/PropertyNode.java40
-rw-r--r--core/java/android/syncml/pim/VBuilder.java62
-rw-r--r--core/java/android/syncml/pim/VDataBuilder.java142
-rw-r--r--core/java/android/syncml/pim/VNode.java29
-rw-r--r--core/java/android/syncml/pim/VParser.java723
-rw-r--r--core/java/android/syncml/pim/package.html6
-rw-r--r--core/java/android/syncml/pim/vcalendar/CalendarStruct.java56
-rw-r--r--core/java/android/syncml/pim/vcalendar/VCalComposer.java189
-rw-r--r--core/java/android/syncml/pim/vcalendar/VCalException.java41
-rw-r--r--core/java/android/syncml/pim/vcalendar/VCalParser.java116
-rw-r--r--core/java/android/syncml/pim/vcalendar/VCalParser_V10.java1628
-rw-r--r--core/java/android/syncml/pim/vcalendar/VCalParser_V20.java186
-rw-r--r--core/java/android/syncml/pim/vcalendar/package.html6
-rw-r--r--core/java/android/syncml/pim/vcard/ContactStruct.java91
-rw-r--r--core/java/android/syncml/pim/vcard/VCardComposer.java350
-rw-r--r--core/java/android/syncml/pim/vcard/VCardException.java41
-rw-r--r--core/java/android/syncml/pim/vcard/VCardParser.java142
-rw-r--r--core/java/android/syncml/pim/vcard/VCardParser_V21.java970
-rw-r--r--core/java/android/syncml/pim/vcard/VCardParser_V30.java157
-rw-r--r--core/java/android/syncml/pim/vcard/package.html6
21 files changed, 4987 insertions, 0 deletions
diff --git a/core/java/android/syncml/package.html b/core/java/android/syncml/package.html
new file mode 100644
index 0000000..cb4ca46
--- /dev/null
+++ b/core/java/android/syncml/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Support classes for SyncML.
+{@hide}
+</BODY>
+</HTML> \ No newline at end of file
diff --git a/core/java/android/syncml/pim/PropertyNode.java b/core/java/android/syncml/pim/PropertyNode.java
new file mode 100644
index 0000000..13d4930
--- /dev/null
+++ b/core/java/android/syncml/pim/PropertyNode.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import android.content.ContentValues;
+
+public class PropertyNode {
+
+ public String propName;
+
+ public String propValue = "";
+
+ public Collection<String> propValue_vector;
+
+ /** Store value as byte[],after decode. */
+ public byte[] propValue_byts;
+
+ /** param store: key=paramType, value=paramValue */
+ public ContentValues paraMap = new ContentValues();
+
+ /** Only for TYPE=??? param store. */
+ public ArrayList<String> paraMap_TYPE = new ArrayList<String>();
+}
diff --git a/core/java/android/syncml/pim/VBuilder.java b/core/java/android/syncml/pim/VBuilder.java
new file mode 100644
index 0000000..822c2ce
--- /dev/null
+++ b/core/java/android/syncml/pim/VBuilder.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.util.Collection;
+
+public interface VBuilder {
+ void start();
+
+ void end();
+
+ /**
+ * @param type
+ * VXX <br>
+ * BEGIN:VXX
+ */
+ void startRecord(String type);
+
+ /** END:VXX */
+ void endRecord();
+
+ void startProperty();
+
+ void endProperty();
+
+ /**
+ * @param name
+ * a.N <br>
+ * a.N
+ */
+ void propertyName(String name);
+
+ /**
+ * @param type
+ * LANGUAGE \ ENCODING <br>
+ * ;LANGUage= \ ;ENCODING=
+ */
+ void propertyParamType(String type);
+
+ /**
+ * @param value
+ * FR-EN \ GBK <br>
+ * FR-EN \ GBK
+ */
+ void propertyParamValue(String value);
+
+ void propertyValues(Collection<String> values);
+}
diff --git a/core/java/android/syncml/pim/VDataBuilder.java b/core/java/android/syncml/pim/VDataBuilder.java
new file mode 100644
index 0000000..f0a0cb9
--- /dev/null
+++ b/core/java/android/syncml/pim/VDataBuilder.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2007 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;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.net.QuotedPrintableCodec;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Store the parse result to custom datastruct: VNode, PropertyNode
+ * Maybe several vcard instance, so use vNodeList to store.
+ * VNode: standy by a vcard instance.
+ * PropertyNode: standy by a property line of a card.
+ */
+public class VDataBuilder implements VBuilder {
+
+ /** type=VNode */
+ public ArrayList<VNode> vNodeList = new ArrayList<VNode>();
+ int nodeListPos = 0;
+ VNode curVNode;
+ PropertyNode curPropNode;
+ String curParamType;
+
+ public void start() {
+ }
+
+ public void end() {
+ }
+
+ public void startRecord(String type) {
+ VNode vnode = new VNode();
+ vnode.parseStatus = 1;
+ vnode.VName = type;
+ vNodeList.add(vnode);
+ nodeListPos = vNodeList.size()-1;
+ curVNode = vNodeList.get(nodeListPos);
+ }
+
+ public void endRecord() {
+ VNode endNode = vNodeList.get(nodeListPos);
+ endNode.parseStatus = 0;
+ while(nodeListPos > 0){
+ nodeListPos--;
+ if((vNodeList.get(nodeListPos)).parseStatus == 1)
+ break;
+ }
+ curVNode = vNodeList.get(nodeListPos);
+ }
+
+ public void startProperty() {
+ // System.out.println("+ startProperty. ");
+ }
+
+ public void endProperty() {
+ // System.out.println("- endProperty. ");
+ }
+
+ public void propertyName(String name) {
+ curPropNode = new PropertyNode();
+ curPropNode.propName = name;
+ }
+
+ public void propertyParamType(String type) {
+ curParamType = 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.
+ replaceAll(" ","").replaceAll("\t","").
+ replaceAll("\r\n","").
+ getBytes());
+ }
+ if(curPropNode.paraMap.getAsString("ENCODING").
+ equalsIgnoreCase("QUOTED-PRINTABLE")){
+ 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();
+ }
+ }
+ }
+ curVNode.propList.add(curPropNode);
+ }
+
+ private String listToString(Collection<String> list){
+ StringBuilder typeListB = new StringBuilder();
+ for (String type : list) {
+ typeListB.append(type).append(";");
+ }
+ int len = typeListB.length();
+ if (len > 0 && typeListB.charAt(len - 1) == ';') {
+ return typeListB.substring(0, len - 1);
+ }
+ return typeListB.toString();
+ }
+
+ public String getResult(){
+ return null;
+ }
+}
+
diff --git a/core/java/android/syncml/pim/VNode.java b/core/java/android/syncml/pim/VNode.java
new file mode 100644
index 0000000..9015415
--- /dev/null
+++ b/core/java/android/syncml/pim/VNode.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.util.ArrayList;
+
+public class VNode {
+
+ public String VName;
+
+ public ArrayList<PropertyNode> propList = new ArrayList<PropertyNode>();
+
+ /** 0:parse over. 1:parsing. */
+ public int parseStatus = 1;
+}
diff --git a/core/java/android/syncml/pim/VParser.java b/core/java/android/syncml/pim/VParser.java
new file mode 100644
index 0000000..df93f38
--- /dev/null
+++ b/core/java/android/syncml/pim/VParser.java
@@ -0,0 +1,723 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * This interface is used to parse the V format files, such as VCard & VCal
+ *
+ */
+abstract public class VParser {
+
+ /**
+ * The buffer used to store input stream
+ */
+ protected String mBuffer = null;
+
+ /** The builder to build parsed data */
+ protected VBuilder mBuilder = null;
+
+ /** The encoding type */
+ protected String mEncoding = null;
+
+ protected final int PARSE_ERROR = -1;
+
+ protected final String mDefaultEncoding = "8BIT";
+
+ /**
+ * If offset reach '\r\n' return 2. Else return PARSE_ERROR.
+ */
+ protected int parseCrlf(int offset) {
+ if (offset >= mBuffer.length())
+ return PARSE_ERROR;
+ char ch = mBuffer.charAt(offset);
+ if (ch == '\r') {
+ offset++;
+ ch = mBuffer.charAt(offset);
+ if (ch == '\n') {
+ return 2;
+ }
+ }
+ return PARSE_ERROR;
+ }
+
+ /**
+ * Parse the given stream
+ *
+ * @param is
+ * The source to parse.
+ * @param encoding
+ * The encoding type.
+ * @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 encoding, VBuilder builder)
+ throws IOException {
+ setInputStream(is, encoding);
+ mBuilder = builder;
+ int ret = 0, offset = 0, sum = 0;
+
+ if (mBuilder != null) {
+ mBuilder.start();
+ }
+ for (;;) {
+ ret = parseVFile(offset); // for next property length
+ if (PARSE_ERROR == ret) {
+ break;
+ } else {
+ offset += ret;
+ sum += ret;
+ }
+ }
+ if (mBuilder != null) {
+ mBuilder.end();
+ }
+ return (mBuffer.length() == sum);
+ }
+
+ /**
+ * Copy the content of input stream and filter the "folding"
+ */
+ protected void setInputStream(InputStream is, String encoding)
+ throws UnsupportedEncodingException {
+ InputStreamReader reader = new InputStreamReader(is, encoding);
+ StringBuilder b = new StringBuilder();
+
+ int ch;
+ try {
+ while ((ch = reader.read()) != -1) {
+ if (ch == '\r') {
+ ch = reader.read();
+ if (ch == '\n') {
+ ch = reader.read();
+ if (ch == ' ' || ch == '\t') {
+ b.append((char) ch);
+ continue;
+ }
+ b.append("\r\n");
+ if (ch == -1) {
+ break;
+ }
+ } else {
+ b.append("\r");
+ }
+ }
+ b.append((char) ch);
+ }
+ mBuffer = b.toString();
+ } catch (Exception e) {
+ return;
+ }
+ return;
+ }
+
+ /**
+ * abstract function, waiting implement.<br>
+ * analyse from offset, return the length of consumed property.
+ */
+ abstract protected int parseVFile(int offset);
+
+ /**
+ * From offset, jump ' ', '\t', '\r\n' sequence, return the length of jump.<br>
+ * 1 * (SPACE / HTAB / CRLF)
+ */
+ protected int parseWsls(int offset) {
+ int ret = 0, sum = 0;
+
+ try {
+ char ch = mBuffer.charAt(offset);
+ if (ch == ' ' || ch == '\t') {
+ sum++;
+ offset++;
+ } else if ((ret = parseCrlf(offset)) != PARSE_ERROR) {
+ offset += ret;
+ sum += ret;
+ } else {
+ return PARSE_ERROR;
+ }
+ for (;;) {
+ ch = mBuffer.charAt(offset);
+ if (ch == ' ' || ch == '\t') {
+ sum++;
+ offset++;
+ } else if ((ret = parseCrlf(offset)) != PARSE_ERROR) {
+ offset += ret;
+ sum += ret;
+ } else {
+ break;
+ }
+ }
+ } catch (IndexOutOfBoundsException e) {
+ ;
+ }
+ if (sum > 0)
+ return sum;
+ return PARSE_ERROR;
+ }
+
+ /**
+ * To determine if the given string equals to the start of the current
+ * string.
+ *
+ * @param offset
+ * The offset in buffer of current string
+ * @param tar
+ * The given string.
+ * @param ignoreCase
+ * To determine case sensitive or not.
+ * @return The consumed characters, otherwise return PARSE_ERROR.
+ */
+ protected int parseString(int offset, final String tar, boolean ignoreCase) {
+ int sum = 0;
+ if (tar == null) {
+ return PARSE_ERROR;
+ }
+
+ if (ignoreCase) {
+ int len = tar.length();
+ try {
+ if (mBuffer.substring(offset, offset + len).equalsIgnoreCase(
+ tar)) {
+ sum = len;
+ } else {
+ return PARSE_ERROR;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ return PARSE_ERROR;
+ }
+
+ } else { /* case sensitive */
+ if (mBuffer.startsWith(tar, offset)) {
+ sum = tar.length();
+ } else {
+ return PARSE_ERROR;
+ }
+ }
+ return sum;
+ }
+
+ /**
+ * Skip the white space in string.
+ */
+ protected int removeWs(int offset) {
+ if (offset >= mBuffer.length())
+ return PARSE_ERROR;
+ int sum = 0;
+ char ch;
+ while ((ch = mBuffer.charAt(offset)) == ' ' || ch == '\t') {
+ offset++;
+ sum++;
+ }
+ return sum;
+ }
+
+ /**
+ * "X-" word, and its value. Return consumed length.
+ */
+ protected int parseXWord(int offset) {
+ int ret = 0, sum = 0;
+ ret = parseString(offset, "X-", true);
+ if (PARSE_ERROR == ret)
+ return PARSE_ERROR;
+ offset += ret;
+ sum += ret;
+
+ ret = parseWord(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ sum += ret;
+ return sum;
+ }
+
+ /**
+ * From offset, parse as :mEncoding ?= 7bit / 8bit / quoted-printable /
+ * base64
+ */
+ protected int parseValue(int offset) {
+ int ret = 0;
+
+ if (mEncoding == null || mEncoding.equalsIgnoreCase("7BIT")
+ || mEncoding.equalsIgnoreCase("8BIT")
+ || mEncoding.toUpperCase().startsWith("X-")) {
+ ret = parse8bit(offset);
+ if (ret != PARSE_ERROR) {
+ return ret;
+ }
+ return PARSE_ERROR;
+ }
+
+ if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+ ret = parseQuotedPrintable(offset);
+ if (ret != PARSE_ERROR) {
+ return ret;
+ }
+ return PARSE_ERROR;
+ }
+
+ if (mEncoding.equalsIgnoreCase("BASE64")) {
+ ret = parseBase64(offset);
+ if (ret != PARSE_ERROR) {
+ return ret;
+ }
+ return PARSE_ERROR;
+ }
+ return PARSE_ERROR;
+ }
+
+ /**
+ * Refer to RFC 1521, 8bit text
+ */
+ protected int parse8bit(int offset) {
+ int index = 0;
+
+ index = mBuffer.substring(offset).indexOf("\r\n");
+
+ if (index == -1)
+ return PARSE_ERROR;
+ else
+ return index;
+
+ }
+
+ /**
+ * Refer to RFC 1521, quoted printable text ([*(ptext / SPACE / TAB) ptext]
+ * ["="] CRLF)
+ */
+ protected int parseQuotedPrintable(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ for (;;) {
+ ret = parsePtext(offset);
+ if (PARSE_ERROR == ret)
+ break;
+ offset += ret;
+ sum += ret;
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+ }
+
+ ret = parseString(offset, "=", false);
+ if (ret != PARSE_ERROR) {
+ // offset += ret;
+ sum += ret;
+ }
+
+ return sum;
+ }
+
+ /**
+ * return 1 or 3 <any ASCII character except "=", SPACE, or TAB>
+ */
+ protected int parsePtext(int offset) {
+ int ret = 0;
+
+ try {
+ char ch = mBuffer.charAt(offset);
+ if (isPrintable(ch) && ch != '=' && ch != ' ' && ch != '\t') {
+ return 1;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ return PARSE_ERROR;
+ }
+
+ ret = parseOctet(offset);
+ if (ret != PARSE_ERROR) {
+ return ret;
+ }
+ return PARSE_ERROR;
+ }
+
+ /**
+ * start with "=" two of (DIGIT / "A" / "B" / "C" / "D" / "E" / "F") <br>
+ * So maybe return 3.
+ */
+ protected int parseOctet(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = parseString(offset, "=", false);
+ if (PARSE_ERROR == ret)
+ return PARSE_ERROR;
+ offset += ret;
+ sum += ret;
+
+ try {
+ int ch = mBuffer.charAt(offset);
+ if (ch == ' ' || ch == '\t')
+ return ++sum;
+ if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')) {
+ offset++;
+ sum++;
+ ch = mBuffer.charAt(offset);
+ if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')) {
+ sum++;
+ return sum;
+ }
+ }
+ } catch (IndexOutOfBoundsException e) {
+ ;
+ }
+ return PARSE_ERROR;
+ }
+
+ /**
+ * Refer to RFC 1521, base64 text The end of the text is marked with two
+ * CRLF sequences
+ */
+ protected int parseBase64(int offset) {
+ int sum = 0;
+ try {
+ for (;;) {
+ char ch;
+ ch = mBuffer.charAt(offset);
+
+ if (ch == '\r') {
+ int ret = parseString(offset, "\r\n\r\n", false);
+ sum += ret;
+ break;
+ } else {
+ /* ignore none base64 character */
+ sum++;
+ offset++;
+ }
+ }
+ } catch (IndexOutOfBoundsException e) {
+ return PARSE_ERROR;
+ }
+ sum -= 2;/* leave one CRLF to parse the end of this property */
+ return sum;
+ }
+
+ /**
+ * Any printable ASCII sequence except [ ]=:.,;
+ */
+ protected int parseWord(int offset) {
+ int sum = 0;
+ try {
+ for (;;) {
+ char ch = mBuffer.charAt(offset);
+ if (!isPrintable(ch))
+ break;
+ if (ch == ' ' || ch == '=' || ch == ':' || ch == '.'
+ || ch == ',' || ch == ';')
+ break;
+ if (ch == '\\') {
+ ch = mBuffer.charAt(offset + 1);
+ if (ch == ';') {
+ offset++;
+ sum++;
+ }
+ }
+ offset++;
+ sum++;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ ;
+ }
+ if (sum == 0)
+ return PARSE_ERROR;
+ return sum;
+ }
+
+ /**
+ * If it is a letter or digit.
+ */
+ protected boolean isLetterOrDigit(char ch) {
+ if (ch >= '0' && ch <= '9')
+ return true;
+ if (ch >= 'a' && ch <= 'z')
+ return true;
+ if (ch >= 'A' && ch <= 'Z')
+ return true;
+ return false;
+ }
+
+ /**
+ * If it is printable in ASCII
+ */
+ protected boolean isPrintable(char ch) {
+ if (ch >= ' ' && ch <= '~')
+ return true;
+ return false;
+ }
+
+ /**
+ * If it is a letter.
+ */
+ protected boolean isLetter(char ch) {
+ if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get a word from current position.
+ */
+ protected String getWord(int offset) {
+ StringBuilder word = new StringBuilder();
+ try {
+ for (;;) {
+ char ch = mBuffer.charAt(offset);
+ if (isLetterOrDigit(ch) || ch == '-') {
+ word.append(ch);
+ offset++;
+ } else {
+ break;
+ }
+ }
+ } catch (IndexOutOfBoundsException e) {
+ ;
+ }
+ return word.toString();
+ }
+
+ /**
+ * If is: "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
+ */
+ protected int parsePValueVal(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = parseString(offset, "INLINE", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "URL", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "CONTENT-ID", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "CID", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "INLINE", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseXWord(offset);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ return PARSE_ERROR;
+ }
+
+ /**
+ * If is: "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word and
+ * set mEncoding.
+ */
+ protected int parsePEncodingVal(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = parseString(offset, "7BIT", true);
+ if (ret != PARSE_ERROR) {
+ mEncoding = "7BIT";
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "8BIT", true);
+ if (ret != PARSE_ERROR) {
+ mEncoding = "8BIT";
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "QUOTED-PRINTABLE", true);
+ if (ret != PARSE_ERROR) {
+ mEncoding = "QUOTED-PRINTABLE";
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "BASE64", true);
+ if (ret != PARSE_ERROR) {
+ mEncoding = "BASE64";
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseXWord(offset);
+ if (ret != PARSE_ERROR) {
+ mEncoding = mBuffer.substring(offset).substring(0, ret);
+ sum += ret;
+ return sum;
+ }
+
+ return PARSE_ERROR;
+ }
+
+ /**
+ * Refer to RFC1521, section 7.1<br>
+ * If is: "us-ascii" / "iso-8859-xxx" / "X-" word
+ */
+ protected int parseCharsetVal(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = parseString(offset, "us-ascii", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "iso-8859-1", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "iso-8859-2", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "iso-8859-3", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "iso-8859-4", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "iso-8859-5", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "iso-8859-6", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "iso-8859-7", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "iso-8859-8", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseString(offset, "iso-8859-9", true);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ ret = parseXWord(offset);
+ if (ret != PARSE_ERROR) {
+ sum += ret;
+ return sum;
+ }
+
+ return PARSE_ERROR;
+ }
+
+ /**
+ * Refer to RFC 1766<br>
+ * like: XXX(sequence letters)-XXX(sequence letters)
+ */
+ protected int parseLangVal(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = parseTag(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ for (;;) {
+ ret = parseString(offset, "-", false);
+ if (PARSE_ERROR == ret) {
+ break;
+ }
+ offset += ret;
+ sum += ret;
+
+ ret = parseTag(offset);
+ if (PARSE_ERROR == ret) {
+ break;
+ }
+ offset += ret;
+ sum += ret;
+ }
+ return sum;
+ }
+
+ /**
+ * From first 8 position, is sequence LETTER.
+ */
+ protected int parseTag(int offset) {
+ int sum = 0, i = 0;
+
+ try {
+ for (i = 0; i < 8; i++) {
+ char ch = mBuffer.charAt(offset);
+ if (!isLetter(ch)) {
+ break;
+ }
+ sum++;
+ offset++;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ ;
+ }
+ if (i == 0) {
+ return PARSE_ERROR;
+ }
+ return sum;
+ }
+
+}
diff --git a/core/java/android/syncml/pim/package.html b/core/java/android/syncml/pim/package.html
new file mode 100644
index 0000000..cb4ca46
--- /dev/null
+++ b/core/java/android/syncml/pim/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Support classes for SyncML.
+{@hide}
+</BODY>
+</HTML> \ No newline at end of file
diff --git a/core/java/android/syncml/pim/vcalendar/CalendarStruct.java b/core/java/android/syncml/pim/vcalendar/CalendarStruct.java
new file mode 100644
index 0000000..3388ada
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/CalendarStruct.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2007 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.vcalendar;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Same comment as ContactStruct.
+ */
+public class CalendarStruct{
+
+ public static class EventStruct{
+ public String description;
+ public String dtend;
+ public String dtstart;
+ public String duration;
+ public String has_alarm;
+ public String last_date;
+ public String rrule;
+ public String status;
+ public String title;
+ public String event_location;
+ public String uid;
+ public List<String> reminderList;
+
+ public void addReminderList(String method){
+ if(reminderList == null)
+ reminderList = new ArrayList<String>();
+ reminderList.add(method);
+ }
+ }
+
+ public String timezone;
+ public List<EventStruct> eventList;
+
+ public void addEventList(EventStruct stru){
+ if(eventList == null)
+ eventList = new ArrayList<EventStruct>();
+ eventList.add(stru);
+ }
+}
diff --git a/core/java/android/syncml/pim/vcalendar/VCalComposer.java b/core/java/android/syncml/pim/vcalendar/VCalComposer.java
new file mode 100644
index 0000000..18b6719
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/VCalComposer.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2007 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.vcalendar;
+
+/**
+ * vCalendar string composer class
+ */
+public class VCalComposer {
+
+ public final static String VERSION_VCALENDAR10 = "vcalendar1.0";
+ public final static String VERSION_VCALENDAR20 = "vcalendar2.0";
+
+ public final static int VERSION_VCAL10_INT = 1;
+ public final static int VERSION_VCAL20_INT = 2;
+
+ private static String mNewLine = "\r\n";
+ private String mVersion = null;
+
+ public VCalComposer() {
+ }
+
+ /**
+ * Create a vCalendar String.
+ * @param struct see more from CalendarStruct class
+ * @param vcalversion MUST be VERSION_VCAL10 /VERSION_VCAL20
+ * @return vCalendar string
+ * @throws VcalException if version is invalid or create failed
+ */
+ public String createVCal(CalendarStruct struct, int vcalversion)
+ throws VCalException{
+
+ StringBuilder returnStr = new StringBuilder();
+
+ //Version check
+ if(vcalversion != 1 && vcalversion != 2)
+ throw new VCalException("version not match 1.0 or 2.0.");
+ if (vcalversion == 1)
+ mVersion = VERSION_VCALENDAR10;
+ else
+ mVersion = VERSION_VCALENDAR20;
+
+ //Build vCalendar:
+ returnStr.append("BEGIN:VCALENDAR").append(mNewLine);
+
+ if(vcalversion == VERSION_VCAL10_INT)
+ returnStr.append("VERSION:1.0").append(mNewLine);
+ else
+ returnStr.append("VERSION:2.0").append(mNewLine);
+
+ returnStr.append("PRODID:vCal ID default").append(mNewLine);
+
+ if(!isNull(struct.timezone)){
+ if(vcalversion == VERSION_VCAL10_INT)
+ returnStr.append("TZ:").append(struct.timezone).append(mNewLine);
+ else//down here MUST have
+ returnStr.append("BEGIN:VTIMEZONE").append(mNewLine).
+ append("TZID:vCal default").append(mNewLine).
+ append("BEGIN:STANDARD").append(mNewLine).
+ append("DTSTART:16010101T000000").append(mNewLine).
+ append("TZOFFSETFROM:").append(struct.timezone).append(mNewLine).
+ append("TZOFFSETTO:").append(struct.timezone).append(mNewLine).
+ append("END:STANDARD").append(mNewLine).
+ append("END:VTIMEZONE").append(mNewLine);
+ }
+ //Build VEVNET
+ for(int i = 0; i < struct.eventList.size(); i++){
+ String str = buildEventStr( struct.eventList.get(i) );
+ returnStr.append(str);
+ }
+
+ //Build VTODO
+ //TODO
+
+ returnStr.append("END:VCALENDAR").append(mNewLine).append(mNewLine);
+
+ return returnStr.toString();
+ }
+
+ private String buildEventStr(CalendarStruct.EventStruct stru){
+
+ StringBuilder strbuf = new StringBuilder();
+
+ strbuf.append("BEGIN:VEVENT").append(mNewLine);
+
+ if(!isNull(stru.uid))
+ strbuf.append("UID:").append(stru.uid).append(mNewLine);
+
+ if(!isNull(stru.description))
+ strbuf.append("DESCRIPTION:").
+ append(foldingString(stru.description)).append(mNewLine);
+
+ if(!isNull(stru.dtend))
+ strbuf.append("DTEND:").append(stru.dtend).append(mNewLine);
+
+ if(!isNull(stru.dtstart))
+ strbuf.append("DTSTART:").append(stru.dtstart).append(mNewLine);
+
+ if(!isNull(stru.duration))
+ strbuf.append("DUE:").append(stru.duration).append(mNewLine);
+
+ if(!isNull(stru.event_location))
+ strbuf.append("LOCATION:").append(stru.event_location).append(mNewLine);
+
+ if(!isNull(stru.last_date))
+ strbuf.append("COMPLETED:").append(stru.last_date).append(mNewLine);
+
+ if(!isNull(stru.rrule))
+ strbuf.append("RRULE:").append(stru.rrule).append(mNewLine);
+
+ if(!isNull(stru.title))
+ strbuf.append("SUMMARY:").append(stru.title).append(mNewLine);
+
+ if(!isNull(stru.status)){
+ String stat = "TENTATIVE";
+ switch (Integer.parseInt(stru.status)){
+ case 0://Calendar.Calendars.STATUS_TENTATIVE
+ stat = "TENTATIVE";
+ break;
+ case 1://Calendar.Calendars.STATUS_CONFIRMED
+ stat = "CONFIRMED";
+ break;
+ case 2://Calendar.Calendars.STATUS_CANCELED
+ stat = "CANCELLED";
+ break;
+ }
+ strbuf.append("STATUS:").append(stat).append(mNewLine);
+ }
+ //Alarm
+ if(!isNull(stru.has_alarm)
+ && stru.reminderList != null
+ && stru.reminderList.size() > 0){
+
+ if (mVersion.equals(VERSION_VCALENDAR10)){
+ String prefix = "";
+ for(String method : stru.reminderList){
+ switch (Integer.parseInt(method)){
+ case 0:
+ prefix = "DALARM";
+ break;
+ case 1:
+ prefix = "AALARM";
+ break;
+ case 2:
+ prefix = "MALARM";
+ break;
+ case 3:
+ default:
+ prefix = "DALARM";
+ break;
+ }
+ strbuf.append(prefix).append(":default").append(mNewLine);
+ }
+ }else {//version 2.0 only support audio-method now.
+ strbuf.append("BEGIN:VALARM").append(mNewLine).
+ append("ACTION:AUDIO").append(mNewLine).
+ append("TRIGGER:-PT10M").append(mNewLine).
+ append("END:VALARM").append(mNewLine);
+ }
+ }
+ strbuf.append("END:VEVENT").append(mNewLine);
+ return strbuf.toString();
+ }
+
+ /** Alter str to folding supported format. */
+ private String foldingString(String str){
+ return str.replaceAll("\r\n", "\n").replaceAll("\n", "\r\n ");
+ }
+
+ /** is null */
+ private boolean isNull(String str){
+ if(str == null || str.trim().equals(""))
+ return true;
+ return false;
+ }
+}
diff --git a/core/java/android/syncml/pim/vcalendar/VCalException.java b/core/java/android/syncml/pim/vcalendar/VCalException.java
new file mode 100644
index 0000000..48ea134
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/VCalException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 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.vcalendar;
+
+public class VCalException extends java.lang.Exception{
+ // constructors
+
+ /**
+ * Constructs a VCalException object
+ */
+
+ public VCalException()
+ {
+ }
+
+ /**
+ * Constructs a VCalException object
+ *
+ * @param message the error message
+ */
+
+ public VCalException( String message )
+ {
+ super( message );
+ }
+
+}
diff --git a/core/java/android/syncml/pim/vcalendar/VCalParser.java b/core/java/android/syncml/pim/vcalendar/VCalParser.java
new file mode 100644
index 0000000..bc2d598
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/VCalParser.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2007 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.vcalendar;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import android.util.Config;
+import android.util.Log;
+
+import android.syncml.pim.VDataBuilder;
+import android.syncml.pim.VParser;
+
+public class VCalParser{
+
+ private final static String TAG = "VCalParser";
+
+ public final static String VERSION_VCALENDAR10 = "vcalendar1.0";
+ public final static String VERSION_VCALENDAR20 = "vcalendar2.0";
+
+ private VParser mParser = null;
+ private String mVersion = null;
+
+ public VCalParser() {
+ }
+
+ public boolean parse(String vcalendarStr, VDataBuilder builder)
+ throws VCalException {
+
+ vcalendarStr = verifyVCal(vcalendarStr);
+ try{
+ boolean isSuccess = mParser.parse(
+ new ByteArrayInputStream(vcalendarStr.getBytes()),
+ "US-ASCII", builder);
+
+ if (!isSuccess) {
+ if (mVersion.equals(VERSION_VCALENDAR10)) {
+ if(Config.LOGD)
+ Log.d(TAG, "Parse failed for vCal 1.0 parser."
+ + " Try to use 2.0 parser.");
+ mVersion = VERSION_VCALENDAR20;
+ return parse(vcalendarStr, builder);
+ }else
+ throw new VCalException("parse failed.(even use 2.0 parser)");
+ }
+ }catch (IOException e){
+ throw new VCalException(e.getMessage());
+ }
+ return true;
+ }
+
+ /**
+ * Verify vCalendar string, and initialize mVersion according to it.
+ * */
+ private String verifyVCal(String vcalStr) {
+
+ //Version check
+ judgeVersion(vcalStr);
+
+ vcalStr = vcalStr.replaceAll("\r\n", "\n");
+ String[] strlist = vcalStr.split("\n");
+
+ StringBuilder replacedStr = new StringBuilder();
+
+ for (int i = 0; i < strlist.length; i++) {
+ if (strlist[i].indexOf(":") < 0) {
+ if (strlist[i].length() == 0 && strlist[i + 1].indexOf(":") > 0)
+ replacedStr.append(strlist[i]).append("\r\n");
+ else
+ replacedStr.append(" ").append(strlist[i]).append("\r\n");
+ } else
+ replacedStr.append(strlist[i]).append("\r\n");
+ }
+ if(Config.LOGD)Log.d(TAG, "After verify:\r\n" + replacedStr.toString());
+
+ return replacedStr.toString();
+ }
+
+ /**
+ * If version not given. Search from vcal string of the VERSION property.
+ * Then instance mParser to appropriate parser.
+ */
+ private void judgeVersion(String vcalStr) {
+
+ if (mVersion == null) {
+ int versionIdx = vcalStr.indexOf("\nVERSION:");
+
+ mVersion = VERSION_VCALENDAR10;
+
+ if (versionIdx != -1){
+ String versionStr = vcalStr.substring(
+ versionIdx, vcalStr.indexOf("\n", versionIdx + 1));
+ if (versionStr.indexOf("2.0") > 0)
+ mVersion = VERSION_VCALENDAR20;
+ }
+ }
+ if (mVersion.equals(VERSION_VCALENDAR10))
+ mParser = new VCalParser_V10();
+ if (mVersion.equals(VERSION_VCALENDAR20))
+ mParser = new VCalParser_V20();
+ }
+}
+
diff --git a/core/java/android/syncml/pim/vcalendar/VCalParser_V10.java b/core/java/android/syncml/pim/vcalendar/VCalParser_V10.java
new file mode 100644
index 0000000..1b251f3
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/VCalParser_V10.java
@@ -0,0 +1,1628 @@
+/*
+ * Copyright (C) 2007 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.vcalendar;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.syncml.pim.VParser;
+
+public class VCalParser_V10 extends VParser {
+
+ /*
+ * The names of the properties whose value are not separated by ";"
+ */
+ private static final HashSet<String> mEvtPropNameGroup1 = new HashSet<String>(
+ Arrays.asList("ATTACH", "ATTENDEE", "DCREATED", "COMPLETED",
+ "DESCRIPTION", "DUE", "DTEND", "EXRULE", "LAST-MODIFIED",
+ "LOCATION", "RNUM", "PRIORITY", "RELATED-TO", "RRULE",
+ "SEQUENCE", "DTSTART", "SUMMARY", "TRANSP", "URL", "UID",
+ // above belong to simprop
+ "CLASS", "STATUS"));
+
+ /*
+ * The names of properties whose value are separated by ";"
+ */
+ private static final HashSet<String> mEvtPropNameGroup2 = new HashSet<String>(
+ Arrays.asList("AALARM", "CATEGORIES", "DALARM", "EXDATE", "MALARM",
+ "PALARM", "RDATE", "RESOURCES"));
+
+ private static final HashSet<String> mValueCAT = new HashSet<String>(Arrays
+ .asList("APPOINTMENT", "BUSINESS", "EDUCATION", "HOLIDAY",
+ "MEETING", "MISCELLANEOUS", "PERSONAL", "PHONE CALL",
+ "SICK DAY", "SPECIAL OCCASION", "TRAVEL", "VACATION"));
+
+ private static final HashSet<String> mValueCLASS = new HashSet<String>(Arrays
+ .asList("PUBLIC", "PRIVATE", "CONFIDENTIAL"));
+
+ private static final HashSet<String> mValueRES = new HashSet<String>(Arrays
+ .asList("CATERING", "CHAIRS", "EASEL", "PROJECTOR", "VCR",
+ "VEHICLE"));
+
+ private static final HashSet<String> mValueSTAT = new HashSet<String>(Arrays
+ .asList("ACCEPTED", "NEEDS ACTION", "SENT", "TENTATIVE",
+ "CONFIRMED", "DECLINED", "COMPLETED", "DELEGATED"));
+
+ /*
+ * The names of properties whose value can contain escape characters
+ */
+ private static final HashSet<String> mEscAllowedProps = new HashSet<String>(
+ Arrays.asList("DESCRIPTION", "SUMMARY", "AALARM", "DALARM",
+ "MALARM", "PALARM"));
+
+ private static final HashMap<String, HashSet<String>> mSpecialValueSetMap =
+ new HashMap<String, HashSet<String>>();
+
+ static {
+ mSpecialValueSetMap.put("CATEGORIES", mValueCAT);
+ mSpecialValueSetMap.put("CLASS", mValueCLASS);
+ mSpecialValueSetMap.put("RESOURCES", mValueRES);
+ mSpecialValueSetMap.put("STATUS", mValueSTAT);
+ }
+
+ public VCalParser_V10() {
+ }
+
+ protected int parseVFile(int offset) {
+ return parseVCalFile(offset);
+ }
+
+ private int parseVCalFile(int offset) {
+ int ret = 0, sum = 0;
+
+ /* remove wsls */
+ while (PARSE_ERROR != (ret = parseWsls(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+
+ ret = parseVCal(offset); // BEGIN:VCAL ... END:VCAL
+ if (PARSE_ERROR != ret) {
+ offset += ret;
+ sum += ret;
+ } else {
+ return PARSE_ERROR;
+ }
+
+ /* remove wsls */
+ while (PARSE_ERROR != (ret = parseWsls(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+ return sum;
+ }
+
+ /**
+ * "BEGIN" [ws] ":" [ws] "VCALENDAR" [ws] 1*crlf calprop calentities [ws]
+ * *crlf "END" [ws] ":" [ws] "VCALENDAR" [ws] 1*CRLF
+ */
+ private int parseVCal(int offset) {
+ int ret = 0, sum = 0;
+
+ /* BEGIN */
+ ret = parseString(offset, "BEGIN", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // ":"
+ ret = parseString(offset, ":", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // "VCALENDAR
+ ret = parseString(offset, "VCALENDAR", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.startRecord("VCALENDAR");
+ }
+
+ /* [ws] */
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // 1*CRLF
+ ret = parseCrlf(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+
+ // calprop
+ ret = parseCalprops(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // calentities
+ ret = parseCalentities(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // *CRLF
+ while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+
+ // "END"
+ ret = parseString(offset, "END", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // ":"
+ // ":"
+ ret = parseString(offset, ":", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // "VCALENDAR"
+ ret = parseString(offset, "VCALENDAR", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.endRecord();
+ }
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // 1 * CRLF
+ ret = parseCrlf(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+
+ return sum;
+ }
+
+ /**
+ * calprops * CRLF calprop / calprop
+ */
+ private int parseCalprops(int offset) {
+ int ret = 0, sum = 0;
+
+ if (mBuilder != null) {
+ mBuilder.startProperty();
+ }
+ ret = parseCalprop(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.endProperty();
+ }
+
+ for (;;) {
+ /* *CRLF */
+ while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+ // follow VEVENT ,it wont reach endProperty
+ if (mBuilder != null) {
+ mBuilder.startProperty();
+ }
+ ret = parseCalprop(offset);
+ if (PARSE_ERROR == ret) {
+ break;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.endProperty();
+ }
+ }
+
+ return sum;
+ }
+
+ /**
+ * calentities *CRLF calentity / calentity
+ */
+ private int parseCalentities(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = parseCalentity(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ for (;;) {
+ /* *CRLF */
+ while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+
+ ret = parseCalentity(offset);
+ if (PARSE_ERROR == ret) {
+ break;
+ }
+ offset += ret;
+ sum += ret;
+ }
+
+ return sum;
+ }
+
+ /**
+ * calprop = DAYLIGHT/ GEO/ PRODID/ TZ/ VERSION
+ */
+ private int parseCalprop(int offset) {
+ int ret = 0;
+
+ ret = parseCalprop0(offset, "DAYLIGHT");
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseCalprop0(offset, "GEO");
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseCalprop0(offset, "PRODID");
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseCalprop0(offset, "TZ");
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseCalprop1(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+ return PARSE_ERROR;
+ }
+
+ /**
+ * evententity / todoentity
+ */
+ private int parseCalentity(int offset) {
+ int ret = 0;
+
+ ret = parseEvententity(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseTodoentity(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+ return PARSE_ERROR;
+
+ }
+
+ /**
+ * propName [params] ":" value CRLF
+ */
+ private int parseCalprop0(int offset, String propName) {
+ int ret = 0, sum = 0, start = 0;
+
+ ret = parseString(offset, propName, true);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyName(propName);
+ }
+
+ ret = parseParams(offset);
+ if (PARSE_ERROR != ret) {
+ offset += ret;
+ sum += ret;
+ }
+
+ ret = parseString(offset, ":", true);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ start = offset;
+ ret = parseValue(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(mBuffer.substring(start, offset));
+ mBuilder.propertyValues(v);
+ }
+
+ ret = parseCrlf(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ sum += ret;
+
+ return sum;
+ }
+
+ /**
+ * "VERSION" [params] ":" "1.0" CRLF
+ */
+ private int parseCalprop1(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = parseString(offset, "VERSION", true);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyName("VERSION");
+ }
+
+ ret = parseParams(offset);
+ if (PARSE_ERROR != ret) {
+ offset += ret;
+ sum += ret;
+ }
+
+ ret = parseString(offset, ":", true);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ ret = parseString(offset, "1.0", true);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add("1.0");
+ mBuilder.propertyValues(v);
+ }
+ ret = parseCrlf(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ sum += ret;
+
+ return sum;
+ }
+
+ /**
+ * "BEGIN" [ws] ":" [ws] "VEVENT" [ws] 1*CRLF entprops [ws] *CRLF "END" [ws]
+ * ":" [ws] "VEVENT" [ws] 1*CRLF
+ */
+ private int parseEvententity(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = parseString(offset, "BEGIN", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // ":"
+ ret = parseString(offset, ":", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // "VEVNET"
+ ret = parseString(offset, "VEVENT", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.startRecord("VEVENT");
+ }
+
+ /* [ws] */
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // 1*CRLF
+ ret = parseCrlf(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+
+ ret = parseEntprops(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // *CRLF
+ while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+
+ // "END"
+ ret = parseString(offset, "END", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // ":"
+ ret = parseString(offset, ":", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // "VEVENT"
+ ret = parseString(offset, "VEVENT", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.endRecord();
+ }
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // 1 * CRLF
+ ret = parseCrlf(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+
+ return sum;
+ }
+
+ /**
+ * "BEGIN" [ws] ":" [ws] "VTODO" [ws] 1*CRLF entprops [ws] *CRLF "END" [ws]
+ * ":" [ws] "VTODO" [ws] 1*CRLF
+ */
+ private int parseTodoentity(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = parseString(offset, "BEGIN", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // ":"
+ ret = parseString(offset, ":", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // "VTODO"
+ ret = parseString(offset, "VTODO", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.startRecord("VTODO");
+ }
+
+ // 1*CRLF
+ ret = parseCrlf(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+
+ ret = parseEntprops(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // *CRLF
+ while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+
+ // "END"
+ ret = parseString(offset, "END", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // ":"
+ ret = parseString(offset, ":", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // "VTODO"
+ ret = parseString(offset, "VTODO", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.endRecord();
+ }
+
+ // [ws]
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ // 1 * CRLF
+ ret = parseCrlf(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+
+ return sum;
+ }
+
+ /**
+ * entprops *CRLF entprop / entprop
+ */
+ private int parseEntprops(int offset) {
+ int ret = 0, sum = 0;
+ if (mBuilder != null) {
+ mBuilder.startProperty();
+ }
+
+ ret = parseEntprop(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.endProperty();
+ }
+
+ for (;;) {
+ while (PARSE_ERROR != (ret = parseCrlf(offset))) {
+ offset += ret;
+ sum += ret;
+ }
+ if (mBuilder != null) {
+ mBuilder.startProperty();
+ }
+
+ ret = parseEntprop(offset);
+ if (PARSE_ERROR == ret) {
+ break;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.endProperty();
+ }
+ }
+ return sum;
+ }
+
+ /**
+ * for VEVENT,VTODO prop. entprop0 / entprop1
+ */
+ private int parseEntprop(int offset) {
+ int ret = 0;
+ ret = parseEntprop0(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseEntprop1(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+ return PARSE_ERROR;
+ }
+
+ /**
+ * Same with card. ";" [ws] paramlist
+ */
+ private int parseParams(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = parseString(offset, ";", true);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ ret = parseParamlist(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ sum += ret;
+
+ return sum;
+ }
+
+ /**
+ * Same with card. paramlist [ws] ";" [ws] param / param
+ */
+ private int parseParamlist(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = parseParam(offset);
+ if (PARSE_ERROR == ret) {
+ 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 (PARSE_ERROR == ret) {
+ return sum;
+ }
+ offsetTemp += ret;
+ sumTemp += ret;
+
+ ret = removeWs(offsetTemp);
+ offsetTemp += ret;
+ sumTemp += ret;
+
+ ret = parseParam(offsetTemp);
+ if (PARSE_ERROR == ret) {
+ break;
+ }
+ offsetTemp += ret;
+ sumTemp += ret;
+
+ // offset = offsetTemp;
+ sum = sumTemp;
+ }
+ return sum;
+ }
+
+ /**
+ * param0 - param7 / knowntype
+ */
+ private int parseParam(int offset) {
+ int ret = 0;
+
+ ret = parseParam0(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseParam1(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseParam2(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseParam3(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseParam4(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseParam5(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseParam6(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseParam7(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ int start = offset;
+ ret = parseKnownType(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyParamType(null);
+ mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ }
+
+ return ret;
+ }
+
+ /**
+ * simprop AND "CLASS" AND "STATUS" The value of these properties are not
+ * seperated by ";"
+ *
+ * [ws] simprop [params] ":" value CRLF
+ */
+ private int parseEntprop0(int offset) {
+ int ret = 0, sum = 0, start = 0;
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ String propName = getWord(offset).toUpperCase();
+ if (!mEvtPropNameGroup1.contains(propName)) {
+ if (PARSE_ERROR == parseXWord(offset))
+ return PARSE_ERROR;
+ }
+ ret = propName.length();
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyName(propName);
+ }
+
+ ret = parseParams(offset);
+ if (PARSE_ERROR != ret) {
+ offset += ret;
+ sum += ret;
+ }
+
+ ret = parseString(offset, ":", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ start = offset;
+ ret = parseValue(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(exportEntpropValue(propName, mBuffer.substring(start,
+ offset)));
+ mBuilder.propertyValues(v);
+ // Filter value,match string, REFER:RFC
+ if (PARSE_ERROR == valueFilter(propName, v))
+ return PARSE_ERROR;
+ }
+
+ ret = parseCrlf(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ sum += ret;
+ return sum;
+ }
+
+ /**
+ * other event prop names except simprop AND "CLASS" AND "STATUS" The value
+ * of these properties are seperated by ";" [ws] proper name [params] ":"
+ * value CRLF
+ */
+ private int parseEntprop1(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ String propName = getWord(offset).toUpperCase();
+ if (!mEvtPropNameGroup2.contains(propName)) {
+ return PARSE_ERROR;
+ }
+ ret = propName.length();
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyName(propName);
+ }
+
+ ret = parseParams(offset);
+ if (PARSE_ERROR != ret) {
+ offset += ret;
+ sum += ret;
+ }
+
+ ret = parseString(offset, ":", false);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ int start = offset;
+ ret = parseValue(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ // mutil-values
+ 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 = exportEntpropValue(propName, 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.propertyValues(v);
+ // Filter value,match string, REFER:RFC
+ if (PARSE_ERROR == valueFilter(propName, v))
+ return PARSE_ERROR;
+ }
+
+ ret = parseCrlf(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ sum += ret;
+ return sum;
+ }
+
+ /**
+ * "TYPE" [ws] = [ws] ptypeval
+ */
+ private int parseParam0(int offset) {
+ int ret = 0, sum = 0, start = offset;
+
+ ret = parseString(offset, "TYPE", true);
+ if (PARSE_ERROR == ret) {
+ 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 (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ start = offset;
+ ret = parsePtypeval(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ }
+ return sum;
+ }
+
+ /**
+ * ["VALUE" [ws] "=" [ws]] pvalueval
+ */
+ private int parseParam1(int offset) {
+ int ret = 0, sum = 0, start = offset;
+ boolean flag = false;
+
+ ret = parseString(offset, "VALUE", true);
+ if (PARSE_ERROR != ret) {
+ offset += ret;
+ sum += ret;
+ flag = true;
+ }
+ if (flag == true && mBuilder != null) {
+ mBuilder.propertyParamType(mBuffer.substring(start, offset));
+ }
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ ret = parseString(offset, "=", true);
+ if (PARSE_ERROR != ret) {
+ if (flag == false) { // "VALUE" does not exist
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ } else {
+ if (flag == true) { // "VALUE" exists
+ return PARSE_ERROR;
+ }
+ }
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ start = offset;
+ ret = parsePValueVal(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ }
+
+ return sum;
+ }
+
+ /** ["ENCODING" [ws] "=" [ws]] pencodingval */
+ private int parseParam2(int offset) {
+ int ret = 0, sum = 0, start = offset;
+ boolean flag = false;
+
+ ret = parseString(offset, "ENCODING", true);
+ if (PARSE_ERROR != ret) {
+ offset += ret;
+ sum += ret;
+ flag = true;
+ }
+ if (flag == true && mBuilder != null) {
+ mBuilder.propertyParamType(mBuffer.substring(start, offset));
+ }
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ ret = parseString(offset, "=", true);
+ if (PARSE_ERROR != ret) {
+ if (flag == false) { // "VALUE" does not exist
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ } else {
+ if (flag == true) { // "VALUE" exists
+ return PARSE_ERROR;
+ }
+ }
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ start = offset;
+ ret = parsePEncodingVal(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ }
+
+ return sum;
+ }
+
+ /**
+ * "CHARSET" [WS] "=" [WS] charsetval
+ */
+ private int parseParam3(int offset) {
+ int ret = 0, sum = 0, start = offset;
+
+ ret = parseString(offset, "CHARSET", true);
+ if (PARSE_ERROR == ret) {
+ 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, "=", true);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ start = offset;
+ ret = parseCharsetVal(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ }
+
+ return sum;
+ }
+
+ /**
+ * "LANGUAGE" [ws] "=" [ws] langval
+ */
+ private int parseParam4(int offset) {
+ int ret = 0, sum = 0, start = offset;
+
+ ret = parseString(offset, "LANGUAGE", true);
+ if (PARSE_ERROR == ret) {
+ 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, "=", true);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ start = offset;
+ ret = parseLangVal(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ }
+
+ return sum;
+ }
+
+ /**
+ * "ROLE" [ws] "=" [ws] roleval
+ */
+ private int parseParam5(int offset) {
+ int ret = 0, sum = 0, start = offset;
+
+ ret = parseString(offset, "ROLE", true);
+ if (PARSE_ERROR == ret) {
+ 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, "=", true);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ start = offset;
+ ret = parseRoleVal(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ }
+
+ return sum;
+ }
+
+ /**
+ * "STATUS" [ws] = [ws] statuval
+ */
+ private int parseParam6(int offset) {
+ int ret = 0, sum = 0, start = offset;
+
+ ret = parseString(offset, "STATUS", true);
+ if (PARSE_ERROR == ret) {
+ 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, "=", true);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ start = offset;
+ ret = parseStatuVal(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ }
+
+ return sum;
+
+ }
+
+ /**
+ * XWord [ws] "=" [ws] word
+ */
+ private int parseParam7(int offset) {
+ int ret = 0, sum = 0, start = offset;
+
+ ret = parseXWord(offset);
+ if (PARSE_ERROR == ret) {
+ 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, "=", true);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ start = offset;
+ ret = parseWord(offset);
+ if (PARSE_ERROR == ret) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ }
+
+ return sum;
+
+ }
+
+ /*
+ * "WAVE" / "PCM" / "VCARD" / XWORD
+ */
+ private int parseKnownType(int offset) {
+ int ret = 0;
+
+ ret = parseString(offset, "WAVE", true);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseString(offset, "PCM", true);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseString(offset, "VCARD", true);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseXWord(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ return PARSE_ERROR;
+ }
+
+ /*
+ * knowntype / Xword
+ */
+ private int parsePtypeval(int offset) {
+ int ret = 0;
+
+ ret = parseKnownType(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseXWord(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ return PARSE_ERROR;
+ }
+
+ /**
+ * "ATTENDEE" / "ORGANIZER" / "OWNER" / XWORD
+ */
+ private int parseRoleVal(int offset) {
+ int ret = 0;
+
+ ret = parseString(offset, "ATTENDEE", true);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseString(offset, "ORGANIZER", true);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseString(offset, "OWNER", true);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseXWord(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ return PARSE_ERROR;
+ }
+
+ /**
+ * "ACCEPTED" / "NEED ACTION" / "SENT" / "TENTATIVE" / "CONFIRMED" /
+ * "DECLINED" / "COMPLETED" / "DELEGATED / XWORD
+ */
+ private int parseStatuVal(int offset) {
+ int ret = 0;
+
+ ret = parseString(offset, "ACCEPTED", true);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseString(offset, "NEED ACTION", true);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseString(offset, "TENTATIVE", true);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+ ret = parseString(offset, "CONFIRMED", true);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+ ret = parseString(offset, "DECLINED", true);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+ ret = parseString(offset, "COMPLETED", true);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+ ret = parseString(offset, "DELEGATED", true);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ ret = parseXWord(offset);
+ if (PARSE_ERROR != ret) {
+ return ret;
+ }
+
+ return PARSE_ERROR;
+ }
+
+ /**
+ * Check 4 special propName and it's value to match Hash.
+ *
+ * @return PARSE_ERROR:value not match. 1:go on,like nothing happen.
+ */
+ private int valueFilter(String propName, ArrayList<String> values) {
+ if (propName == null || propName.equals("") || values == null
+ || values.isEmpty())
+ return 1; // go on, like nothing happen.
+
+ if (mSpecialValueSetMap.containsKey(propName)) {
+ for (String value : values) {
+ if (!mSpecialValueSetMap.get(propName).contains(value)) {
+ if (!value.startsWith("X-"))
+ return PARSE_ERROR;
+ }
+ }
+ }
+
+ return 1;
+ }
+
+ /**
+ *
+ * Translate escape characters("\\", "\;") which define in vcalendar1.0
+ * spec. But for fault tolerance, we will translate "\:" and "\,", which
+ * isn't define in vcalendar1.0 explicitly, as the same behavior as other
+ * client.
+ *
+ * Though vcalendar1.0 spec does not defined the value of property
+ * "description", "summary", "aalarm", "dalarm", "malarm" and "palarm" could
+ * contain escape characters, we do support escape characters in these
+ * properties.
+ *
+ * @param str:
+ * the value string will be translated.
+ * @return the string which do not contain any escape character in
+ * vcalendar1.0
+ */
+ private String exportEntpropValue(String propName, String str) {
+ if (null == propName || null == str)
+ return null;
+ if ("".equals(propName) || "".equals(str))
+ return "";
+
+ if (!mEscAllowedProps.contains(propName))
+ return str;
+
+ String tmp = str.replace("\\\\", "\n\r\n");
+ tmp = tmp.replace("\\;", ";");
+ tmp = tmp.replace("\\:", ":");
+ tmp = tmp.replace("\\,", ",");
+ tmp = tmp.replace("\n\r\n", "\\");
+ return tmp;
+ }
+}
diff --git a/core/java/android/syncml/pim/vcalendar/VCalParser_V20.java b/core/java/android/syncml/pim/vcalendar/VCalParser_V20.java
new file mode 100644
index 0000000..5748379
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/VCalParser_V20.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2007 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.vcalendar;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+
+import android.syncml.pim.VBuilder;
+
+public class VCalParser_V20 extends VCalParser_V10 {
+ private static final String V10LINEBREAKER = "\r\n";
+
+ private static final HashSet<String> acceptableComponents = new HashSet<String>(
+ Arrays.asList("VEVENT", "VTODO", "VALARM", "VTIMEZONE"));
+
+ private static final HashSet<String> acceptableV20Props = new HashSet<String>(
+ Arrays.asList("DESCRIPTION", "DTEND", "DTSTART", "DUE",
+ "COMPLETED", "RRULE", "STATUS", "SUMMARY", "LOCATION"));
+
+ private boolean hasTZ = false; // MUST only have one TZ property
+
+ private String[] lines;
+
+ private int index;
+
+ @Override
+ public boolean parse(InputStream is, String encoding, VBuilder builder)
+ throws IOException {
+ // get useful info for android calendar, and alter to vcal 1.0
+ byte[] bytes = new byte[is.available()];
+ is.read(bytes);
+ String scStr = new String(bytes);
+ StringBuilder v10str = new StringBuilder("");
+
+ lines = splitProperty(scStr);
+ index = 0;
+
+ if ("BEGIN:VCALENDAR".equals(lines[index]))
+ v10str.append("BEGIN:VCALENDAR" + V10LINEBREAKER);
+ else
+ return false;
+ index++;
+ if (false == parseV20Calbody(lines, v10str)
+ || index > lines.length - 1)
+ return false;
+
+ if (lines.length - 1 == index && "END:VCALENDAR".equals(lines[index]))
+ v10str.append("END:VCALENDAR" + V10LINEBREAKER);
+ else
+ return false;
+
+ return super.parse(
+ // use vCal 1.0 parser
+ new ByteArrayInputStream(v10str.toString().getBytes()),
+ encoding, builder);
+ }
+
+ /**
+ * Parse and pick acceptable iCalendar body and translate it to
+ * calendarV1.0 format.
+ * @param lines iCalendar components/properties line list.
+ * @param buffer calendarV10 format string buffer
+ * @return true for success, or false
+ */
+ private boolean parseV20Calbody(String[] lines, StringBuilder buffer) {
+ try {
+ while (!"VERSION:2.0".equals(lines[index]))
+ index++;
+ buffer.append("VERSION:1.0" + V10LINEBREAKER);
+
+ index++;
+ for (; index < lines.length - 1; index++) {
+ String[] keyAndValue = lines[index].split(":", 2);
+ String key = keyAndValue[0];
+ String value = keyAndValue[1];
+
+ if ("BEGIN".equals(key.trim())) {
+ if (!key.equals(key.trim()))
+ return false; // MUST be "BEGIN:componentname"
+ index++;
+ if (false == parseV20Component(value, buffer))
+ return false;
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Parse and pick acceptable calendar V2.0's component and translate it to
+ * V1.0 format.
+ * @param compName component name
+ * @param buffer calendarV10 format string buffer
+ * @return true for success, or false
+ * @throws ArrayIndexOutOfBoundsException
+ */
+ private boolean parseV20Component(String compName, StringBuilder buffer)
+ throws ArrayIndexOutOfBoundsException {
+ String endTag = "END:" + compName;
+ String[] propAndValue;
+ String propName, value;
+
+ if (acceptableComponents.contains(compName)) {
+ if ("VEVENT".equals(compName) || "VTODO".equals(compName)) {
+ buffer.append("BEGIN:" + compName + V10LINEBREAKER);
+ while (!endTag.equals(lines[index])) {
+ propAndValue = lines[index].split(":", 2);
+ propName = propAndValue[0].split(";", 2)[0];
+ value = propAndValue[1];
+
+ if ("".equals(lines[index]))
+ buffer.append(V10LINEBREAKER);
+ else if (acceptableV20Props.contains(propName)) {
+ buffer.append(propName + ":" + value + V10LINEBREAKER);
+ } else if ("BEGIN".equals(propName.trim())) {
+ // MUST be BEGIN:VALARM
+ if (propName.equals(propName.trim())
+ && "VALARM".equals(value)) {
+ buffer.append("AALARM:default" + V10LINEBREAKER);
+ while (!"END:VALARM".equals(lines[index]))
+ index++;
+ } else
+ return false;
+ }
+ index++;
+ } // end while
+ buffer.append(endTag + V10LINEBREAKER);
+ } else if ("VALARM".equals(compName)) { // VALARM component MUST
+ // only appear within either VEVENT or VTODO
+ return false;
+ } else if ("VTIMEZONE".equals(compName)) {
+ do {
+ if (false == hasTZ) {// MUST only have 1 time TZ property
+ propAndValue = lines[index].split(":", 2);
+ propName = propAndValue[0].split(";", 2)[0];
+
+ if ("TZOFFSETFROM".equals(propName)) {
+ value = propAndValue[1];
+ buffer.append("TZ" + ":" + value + V10LINEBREAKER);
+ hasTZ = true;
+ }
+ }
+ index++;
+ } while (!endTag.equals(lines[index]));
+ } else
+ return false;
+ } else {
+ while (!endTag.equals(lines[index]))
+ index++;
+ }
+
+ return true;
+ }
+
+ /** split ever property line to String[], not split folding line. */
+ 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;
+ }
+}
diff --git a/core/java/android/syncml/pim/vcalendar/package.html b/core/java/android/syncml/pim/vcalendar/package.html
new file mode 100644
index 0000000..cb4ca46
--- /dev/null
+++ b/core/java/android/syncml/pim/vcalendar/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Support classes for SyncML.
+{@hide}
+</BODY>
+</HTML> \ No newline at end of file
diff --git a/core/java/android/syncml/pim/vcard/ContactStruct.java b/core/java/android/syncml/pim/vcard/ContactStruct.java
new file mode 100644
index 0000000..8d9b7fa
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/ContactStruct.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * The parameter class of VCardCreator.
+ * This class standy by the person-contact in
+ * Android system, we must use this class instance as parameter to transmit to
+ * VCardCreator so that create vCard string.
+ */
+// TODO: rename the class name, next step
+public class ContactStruct {
+ public String company;
+ /** MUST exist */
+ public String name;
+ /** maybe folding */
+ public String notes;
+ /** maybe folding */
+ public String title;
+ /** binary bytes of pic. */
+ public byte[] photoBytes;
+ /** mime_type col of images table */
+ public String photoType;
+ /** Only for GET. Use addPhoneList() to PUT. */
+ public List<PhoneData> phoneList;
+ /** Only for GET. Use addContactmethodList() to PUT. */
+ public List<ContactMethod> contactmethodList;
+
+ public static class PhoneData{
+ /** maybe folding */
+ public String data;
+ public String type;
+ public String label;
+ }
+
+ public static class ContactMethod{
+ public String kind;
+ public String type;
+ public String data;
+ public String label;
+ }
+
+ /**
+ * Add a phone info to phoneList.
+ * @param data phone number
+ * @param type type col of content://contacts/phones
+ * @param label lable col of content://contacts/phones
+ */
+ public void addPhone(String data, String type, String label){
+ if(phoneList == null)
+ phoneList = new ArrayList<PhoneData>();
+ PhoneData st = new PhoneData();
+ st.data = data;
+ st.type = type;
+ st.label = label;
+ phoneList.add(st);
+ }
+ /**
+ * Add a contactmethod info to contactmethodList.
+ * @param data contact data
+ * @param type type col of content://contacts/contact_methods
+ */
+ public void addContactmethod(String kind, String data, String type,
+ String label){
+ if(contactmethodList == null)
+ contactmethodList = new ArrayList<ContactMethod>();
+ ContactMethod st = new ContactMethod();
+ st.kind = kind;
+ st.data = data;
+ st.type = type;
+ st.label = label;
+ contactmethodList.add(st);
+ }
+}
diff --git a/core/java/android/syncml/pim/vcard/VCardComposer.java b/core/java/android/syncml/pim/vcard/VCardComposer.java
new file mode 100644
index 0000000..05e8f40
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardComposer.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.codec.binary.Base64;
+
+import android.provider.Contacts;
+import android.syncml.pim.vcard.ContactStruct.PhoneData;
+
+/**
+ * Compose VCard string
+ */
+public class VCardComposer {
+ final public static int VERSION_VCARD21_INT = 1;
+
+ final public static int VERSION_VCARD30_INT = 2;
+
+ /**
+ * A new line
+ */
+ private String mNewline;
+
+ /**
+ * The composed string
+ */
+ private StringBuilder mResult;
+
+ /**
+ * The email's type
+ */
+ static final private HashSet<String> emailTypes = new HashSet<String>(
+ Arrays.asList("CELL", "AOL", "APPLELINK", "ATTMAIL", "CIS",
+ "EWORLD", "INTERNET", "IBMMAIL", "MCIMAIL", "POWERSHARE",
+ "PRODIGY", "TLX", "X400"));
+
+ static final private HashSet<String> phoneTypes = new HashSet<String>(
+ Arrays.asList("PREF", "WORK", "HOME", "VOICE", "FAX", "MSG",
+ "CELL", "PAGER", "BBS", "MODEM", "CAR", "ISDN", "VIDEO"));
+
+ static final private String TAG = "VCardComposer";
+
+ public VCardComposer() {
+ }
+
+ private static final HashMap<Integer, String> phoneTypeMap = new HashMap<Integer, String>();
+
+ private static final HashMap<Integer, String> emailTypeMap = new HashMap<Integer, String>();
+
+ static {
+ phoneTypeMap.put(Contacts.Phones.TYPE_HOME, "HOME");
+ phoneTypeMap.put(Contacts.Phones.TYPE_MOBILE, "CELL");
+ phoneTypeMap.put(Contacts.Phones.TYPE_WORK, "WORK");
+ // FAX_WORK not exist in vcard spec. The approximate is the combine of
+ // WORK and FAX, here only map to FAX
+ phoneTypeMap.put(Contacts.Phones.TYPE_FAX_WORK, "WORK;FAX");
+ phoneTypeMap.put(Contacts.Phones.TYPE_FAX_HOME, "HOME;FAX");
+ phoneTypeMap.put(Contacts.Phones.TYPE_PAGER, "PAGER");
+ phoneTypeMap.put(Contacts.Phones.TYPE_OTHER, "X-OTHER");
+ emailTypeMap.put(Contacts.ContactMethods.TYPE_HOME, "HOME");
+ emailTypeMap.put(Contacts.ContactMethods.TYPE_WORK, "WORK");
+ }
+
+ /**
+ * Create a vCard String.
+ *
+ * @param struct
+ * see more from ContactStruct class
+ * @param vcardversion
+ * MUST be VERSION_VCARD21 /VERSION_VCARD30
+ * @return vCard string
+ * @throws VCardException
+ * struct.name is null /vcardversion not match
+ */
+ public String createVCard(ContactStruct struct, int vcardversion)
+ throws VCardException {
+
+ mResult = new StringBuilder();
+ // check exception:
+ if (struct.name == null || struct.name.trim().equals("")) {
+ throw new VCardException(" struct.name MUST have value.");
+ }
+ if (vcardversion == VERSION_VCARD21_INT) {
+ mNewline = "\r\n";
+ } else if (vcardversion == VERSION_VCARD30_INT) {
+ mNewline = "\n";
+ } else {
+ throw new VCardException(
+ " version not match VERSION_VCARD21 or VERSION_VCARD30.");
+ }
+ // build vcard:
+ mResult.append("BEGIN:VCARD").append(mNewline);
+
+ if (vcardversion == VERSION_VCARD21_INT) {
+ mResult.append("VERSION:2.1").append(mNewline);
+ } else {
+ mResult.append("VERSION:3.0").append(mNewline);
+ }
+
+ if (!isNull(struct.name)) {
+ appendNameStr(struct.name);
+ }
+
+ if (!isNull(struct.company)) {
+ mResult.append("ORG:").append(struct.company).append(mNewline);
+ }
+
+ if (!isNull(struct.notes)) {
+ mResult.append("NOTE:").append(
+ foldingString(struct.notes, vcardversion)).append(mNewline);
+ }
+
+ if (!isNull(struct.title)) {
+ mResult.append("TITLE:").append(
+ foldingString(struct.title, vcardversion)).append(mNewline);
+ }
+
+ if (struct.photoBytes != null) {
+ appendPhotoStr(struct.photoBytes, struct.photoType, vcardversion);
+ }
+
+ if (struct.phoneList != null) {
+ appendPhoneStr(struct.phoneList, vcardversion);
+ }
+
+ if (struct.contactmethodList != null) {
+ appendContactMethodStr(struct.contactmethodList, vcardversion);
+ }
+
+ mResult.append("END:VCARD").append(mNewline);
+ return mResult.toString();
+ }
+
+ /**
+ * Alter str to folding supported format.
+ *
+ * @param str
+ * the string to be folded
+ * @param version
+ * the vcard version
+ * @return the folded string
+ */
+ private String foldingString(String str, int version) {
+ if (str.endsWith("\r\n")) {
+ str = str.substring(0, str.length() - 2);
+ } else if (str.endsWith("\n")) {
+ str = str.substring(0, str.length() - 1);
+ } else {
+ return null;
+ }
+
+ str = str.replaceAll("\r\n", "\n");
+ if (version == VERSION_VCARD21_INT) {
+ return str.replaceAll("\n", "\r\n ");
+ } else if (version == VERSION_VCARD30_INT) {
+ return str.replaceAll("\n", "\n ");
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Build LOGO property. format LOGO's param and encode value as base64.
+ *
+ * @param bytes
+ * the binary string to be converted
+ * @param type
+ * the type of the content
+ * @param version
+ * the version of vcard
+ */
+ private void appendPhotoStr(byte[] bytes, String type, int version)
+ throws VCardException {
+ String value, apptype, encodingStr;
+ try {
+ value = foldingString(new String(Base64.encodeBase64(bytes, true)),
+ version);
+ } catch (Exception e) {
+ throw new VCardException(e.getMessage());
+ }
+
+ if (isNull(type)) {
+ type = "image/jpeg";
+ }
+ if (type.indexOf("jpeg") > 0) {
+ apptype = "JPEG";
+ } else if (type.indexOf("gif") > 0) {
+ apptype = "GIF";
+ } else if (type.indexOf("bmp") > 0) {
+ apptype = "BMP";
+ } else {
+ apptype = type.substring(type.indexOf("/")).toUpperCase();
+ }
+
+ mResult.append("LOGO;TYPE=").append(apptype);
+ if (version == VERSION_VCARD21_INT) {
+ encodingStr = ";ENCODING=BASE64:";
+ value = value + mNewline;
+ } else if (version == VERSION_VCARD30_INT) {
+ encodingStr = ";ENCODING=b:";
+ } else {
+ return;
+ }
+ mResult.append(encodingStr).append(value).append(mNewline);
+ }
+
+ private boolean isNull(String str) {
+ if (str == null || str.trim().equals("")) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Build FN and N property. format N's value.
+ *
+ * @param name
+ * the name of the contact
+ */
+ private void appendNameStr(String name) {
+ mResult.append("FN:").append(name).append(mNewline);
+ mResult.append("N:").append(name).append(mNewline);
+ /*
+ * if(name.indexOf(";") > 0)
+ * mResult.append("N:").append(name).append(mNewline); else
+ * if(name.indexOf(" ") > 0) mResult.append("N:").append(name.replace(' ',
+ * ';')). append(mNewline); else
+ * mResult.append("N:").append(name).append("; ").append(mNewline);
+ */
+ }
+
+ /** Loop append TEL property. */
+ private void appendPhoneStr(List<ContactStruct.PhoneData> phoneList,
+ int version) {
+ HashMap<String, String> numMap = new HashMap<String, String>();
+ String joinMark = version == VERSION_VCARD21_INT ? ";" : ",";
+
+ for (ContactStruct.PhoneData phone : phoneList) {
+ String type;
+ if (!isNull(phone.data)) {
+ type = getPhoneTypeStr(phone);
+ if (version == VERSION_VCARD30_INT && type.indexOf(";") != -1) {
+ type = type.replace(";", ",");
+ }
+ if (numMap.containsKey(phone.data)) {
+ type = numMap.get(phone.data) + joinMark + type;
+ }
+ numMap.put(phone.data, type);
+ }
+ }
+
+ for (Map.Entry<String, String> num : numMap.entrySet()) {
+ if (version == VERSION_VCARD21_INT) {
+ mResult.append("TEL;");
+ } else { // vcard3.0
+ mResult.append("TEL;TYPE=");
+ }
+ mResult.append(num.getValue()).append(":").append(num.getKey())
+ .append(mNewline);
+ }
+ }
+
+ private String getPhoneTypeStr(PhoneData phone) {
+
+ int phoneType = Integer.parseInt(phone.type);
+ String typeStr, label;
+
+ if (phoneTypeMap.containsKey(phoneType)) {
+ typeStr = phoneTypeMap.get(phoneType);
+ } else if (phoneType == Contacts.Phones.TYPE_CUSTOM) {
+ label = phone.label.toUpperCase();
+ if (phoneTypes.contains(label) || label.startsWith("X-")) {
+ typeStr = label;
+ } else {
+ typeStr = "X-CUSTOM-" + label;
+ }
+ } else {
+ // TODO: need be updated with the provider's future changes
+ typeStr = "VOICE"; // the default type is VOICE in spec.
+ }
+ return typeStr;
+ }
+
+ /** Loop append ADR / EMAIL property. */
+ private void appendContactMethodStr(
+ List<ContactStruct.ContactMethod> contactMList, int version) {
+
+ HashMap<String, String> emailMap = new HashMap<String, String>();
+ String joinMark = version == VERSION_VCARD21_INT ? ";" : ",";
+ for (ContactStruct.ContactMethod contactMethod : contactMList) {
+ // same with v2.1 and v3.0
+ switch (Integer.parseInt(contactMethod.kind)) {
+ case Contacts.KIND_EMAIL:
+ String mailType = "INTERNET";
+ if (!isNull(contactMethod.data)) {
+ int methodType = new Integer(contactMethod.type).intValue();
+ if (emailTypeMap.containsKey(methodType)) {
+ mailType = emailTypeMap.get(methodType);
+ } else if (emailTypes.contains(contactMethod.label
+ .toUpperCase())) {
+ mailType = contactMethod.label.toUpperCase();
+ }
+ if (emailMap.containsKey(contactMethod.data)) {
+ mailType = emailMap.get(contactMethod.data) + joinMark
+ + mailType;
+ }
+ emailMap.put(contactMethod.data, mailType);
+ }
+ break;
+ case Contacts.KIND_POSTAL:
+ if (!isNull(contactMethod.data)) {
+ mResult.append("ADR;TYPE=POSTAL:").append(
+ foldingString(contactMethod.data, version)).append(
+ mNewline);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ for (Map.Entry<String, String> email : emailMap.entrySet()) {
+ if (version == VERSION_VCARD21_INT) {
+ mResult.append("EMAIL;");
+ } else {
+ mResult.append("EMAIL;TYPE=");
+ }
+ mResult.append(email.getValue()).append(":").append(email.getKey())
+ .append(mNewline);
+ }
+ }
+}
diff --git a/core/java/android/syncml/pim/vcard/VCardException.java b/core/java/android/syncml/pim/vcard/VCardException.java
new file mode 100644
index 0000000..35b31ec
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 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;
+
+public class VCardException extends java.lang.Exception{
+ // constructors
+
+ /**
+ * Constructs a VCardException object
+ */
+
+ public VCardException()
+ {
+ }
+
+ /**
+ * Constructs a VCardException object
+ *
+ * @param message the error message
+ */
+
+ public VCardException( String message )
+ {
+ super( message );
+ }
+
+}
diff --git a/core/java/android/syncml/pim/vcard/VCardParser.java b/core/java/android/syncml/pim/vcard/VCardParser.java
new file mode 100644
index 0000000..3926243
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardParser.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2008 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;
+
+import android.syncml.pim.VDataBuilder;
+import android.syncml.pim.VParser;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+public class VCardParser {
+
+ VParser mParser = null;
+
+ public final static String VERSION_VCARD21 = "vcard2.1";
+
+ public final static String VERSION_VCARD30 = "vcard3.0";
+
+ final public static int VERSION_VCARD21_INT = 1;
+
+ final public static int VERSION_VCARD30_INT = 2;
+
+ String mVersion = null;
+
+ static final private String TAG = "VCardParser";
+
+ public VCardParser() {
+ }
+
+ /**
+ * If version not given. Search from vcard string of the VERSION property.
+ * Then instance mParser to appropriate parser.
+ *
+ * @param vcardStr
+ * the content of vcard data
+ */
+ private void judgeVersion(String vcardStr) {
+ if (mVersion == null) {// auto judge
+ int verIdx = vcardStr.indexOf("\nVERSION:");
+ if (verIdx == -1) // if not have VERSION, v2.1 default
+ mVersion = VERSION_VCARD21;
+ else {
+ String verStr = vcardStr.substring(verIdx, vcardStr.indexOf(
+ "\n", verIdx + 1));
+ if (verStr.indexOf("2.1") > 0)
+ mVersion = VERSION_VCARD21;
+ else if (verStr.indexOf("3.0") > 0)
+ mVersion = VERSION_VCARD30;
+ else
+ mVersion = VERSION_VCARD21;
+ }
+ }
+ if (mVersion.equals(VERSION_VCARD21))
+ mParser = new VCardParser_V21();
+ if (mVersion.equals(VERSION_VCARD30))
+ mParser = new VCardParser_V30();
+ }
+
+ /**
+ * To make sure the vcard string has proper wrap character
+ *
+ * @param vcardStr
+ * the string to be checked
+ * @return string after verified
+ */
+ private String verifyVCard(String vcardStr) {
+ this.judgeVersion(vcardStr);
+ // -- indent line:
+ vcardStr = vcardStr.replaceAll("\r\n", "\n");
+ String[] strlist = vcardStr.split("\n");
+ StringBuilder v21str = new StringBuilder("");
+ for (int i = 0; i < strlist.length; i++) {
+ if (strlist[i].indexOf(":") < 0) {
+ if (strlist[i].length() == 0 && strlist[i + 1].indexOf(":") > 0)
+ v21str.append(strlist[i]).append("\r\n");
+ else
+ v21str.append(" ").append(strlist[i]).append("\r\n");
+ } else
+ v21str.append(strlist[i]).append("\r\n");
+ }
+ return v21str.toString();
+ }
+
+ /**
+ * Set current version
+ *
+ * @param version
+ * the new version
+ */
+ private void setVersion(String version) {
+ this.mVersion = version;
+ }
+
+ /**
+ * Parse the given vcard string
+ *
+ * @param vcardStr
+ * to content to be parsed
+ * @param builder
+ * the data builder to hold data
+ * @return true if the string is successfully parsed, else return false
+ * @throws VCardException
+ * @throws IOException
+ */
+ public boolean parse(String vcardStr, VDataBuilder builder)
+ throws VCardException, IOException {
+
+ vcardStr = this.verifyVCard(vcardStr);
+
+ boolean isSuccess = mParser.parse(new ByteArrayInputStream(vcardStr
+ .getBytes()), "US-ASCII", builder);
+ if (!isSuccess) {
+ if (mVersion.equals(VERSION_VCARD21)) {
+ if (Config.LOGD)
+ Log.d(TAG, "Parse failed for vCard 2.1 parser."
+ + " Try to use 3.0 parser.");
+
+ this.setVersion(VERSION_VCARD30);
+
+ return this.parse(vcardStr, builder);
+ }
+ throw new VCardException("parse failed.(even use 3.0 parser)");
+ }
+ return true;
+ }
+}
diff --git a/core/java/android/syncml/pim/vcard/VCardParser_V21.java b/core/java/android/syncml/pim/vcard/VCardParser_V21.java
new file mode 100644
index 0000000..b6fa032
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardParser_V21.java
@@ -0,0 +1,970 @@
+/*
+ * Copyright (C) 2008 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;
+
+import android.syncml.pim.VParser;
+
+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 {
+
+ /** Store the known-type */
+ private static final HashSet<String> mKnownTypeSet = new HashSet<String>(
+ Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
+ "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
+ "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
+ "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
+ "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
+ "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"));
+
+ /**
+ * Create a new VCard parser.
+ */
+ public VCardParser_V21() {
+ super();
+ }
+
+ /**
+ * Parse the file at the given position
+ *
+ * @param offset
+ * the given position to parse
+ * @return vcard length
+ */
+ protected int parseVFile(int offset) {
+ return parseVCardFile(offset);
+ }
+
+ /**
+ * [wsls] vcard [wsls]
+ */
+ 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;
+ }
+
+ /* remove \t \r\n */
+ while ((ret = parseWsls(offset)) != PARSE_ERROR) {
+ offset += ret;
+ sum += ret;
+ }
+ return sum;
+ }
+
+ /**
+ * "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [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;
+ }
+ 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;
+ 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;
+ if (mBuilder != null) {
+ mBuilder.endRecord();
+ }
+
+ return sum;
+ }
+
+ /**
+ * items *CRLF item / item
+ */
+ private int parseItems(int offset) {
+ /* items *CRLF item / item */
+ int ret = 0, sum = 0;
+
+ 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;
+ }
+ // 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();
+ }
+ }
+
+ return sum;
+ }
+
+ /**
+ * item0 / item1 / item2
+ */
+ 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;
+ 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.propertyValues(v);
+ }
+
+ ret = parseCrlf(offset);
+ if (ret == PARSE_ERROR) {
+ return PARSE_ERROR;
+ }
+ sum += ret;
+
+ return sum;
+ }
+
+ /** [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;
+ }
+ 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;
+ }
+
+ /** ";" [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;
+ }
+ sum += ret;
+
+ return sum;
+ }
+
+ /**
+ * paramlist [ws] ";" [ws] param / param
+ */
+ 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;
+ }
+ offsetTemp += ret;
+ sumTemp += ret;
+
+ // offset = offsetTemp;
+ sum = sumTemp;
+ }
+ return sum;
+ }
+
+ /**
+ * param0 / param1 / param2 / param3 / param4 / param5 / knowntype<BR>
+ * TYPE / VALUE / ENDCODING / CHARSET / LANGUAGE ...
+ */
+ 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;
+ if (mBuilder != null) {
+ mBuilder.propertyParamType(null);
+ mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ }
+
+ 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;
+ }
+ offset += ret;
+ sum += ret;
+
+ ret = removeWs(offset);
+ offset += ret;
+ sum += ret;
+
+ start = offset;
+ ret = parsePTypeVal(offset);
+ if (ret == PARSE_ERROR) {
+ return PARSE_ERROR;
+ }
+ offset += ret;
+ sum += ret;
+ if (mBuilder != null) {
+ mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ }
+
+ 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;
+ if (mBuilder != null) {
+ mBuilder.propertyParamValue(mBuffer.substring(start, offset));
+ }
+
+ 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));
+ }
+
+ 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));
+ }
+
+ 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));
+ }
+
+ return sum;
+
+ }
+
+ /** "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;
+ }
+ 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));
+ }
+
+ return sum;
+ }
+
+ /**
+ * knowntype: "DOM" / "INTL" / ...
+ */
+ private int parseKnownType(int offset) {
+ String word = getWord(offset);
+
+ if (mKnownTypeSet.contains(word.toUpperCase())) {
+ return word.length();
+ }
+ return PARSE_ERROR;
+ }
+
+ /** 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;
+ }
+
+ /** "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;
+ }
+
+ /** groups "." word / word */
+ private int parseGroups(int offset) {
+ int ret = 0, sum = 0;
+
+ ret = parseWord(offset);
+ if (ret == PARSE_ERROR) {
+ return PARSE_ERROR;
+ }
+ 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;
+ }
+ return sum;
+ }
+
+ /**
+ * 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;
+ }
+}
diff --git a/core/java/android/syncml/pim/vcard/VCardParser_V30.java b/core/java/android/syncml/pim/vcard/VCardParser_V30.java
new file mode 100644
index 0000000..c56cfed
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardParser_V30.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2008 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;
+
+import android.syncml.pim.VBuilder;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+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
+ */
+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>();
+
+ static {
+ propV30ToV21Map.put("PHOTO", "PHOTO");
+ propV30ToV21Map.put("LOGO", "PHOTO");
+ }
+
+ @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);
+ } else {
+ return false;
+ }
+
+ 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));
+ }
+ }
+ }// 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
+ */
+ private String mapContentlineV30ToV21(String propName, String params,
+ String value) {
+ String result;
+
+ if (propV30ToV21Map.containsKey(propName)) {
+ result = propV30ToV21Map.get(propName);
+ } else {
+ result = propName;
+ }
+ // 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;
+ }
+
+ /**
+ * Split ever property line to Stringp[], not split folding line.
+ *
+ * @param scStr
+ * the string to be splitted
+ * @return a list of splitted string
+ */
+ 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;
+ }
+}
diff --git a/core/java/android/syncml/pim/vcard/package.html b/core/java/android/syncml/pim/vcard/package.html
new file mode 100644
index 0000000..cb4ca46
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Support classes for SyncML.
+{@hide}
+</BODY>
+</HTML> \ No newline at end of file