diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 | 
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 | 
| commit | 54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch) | |
| tree | 35051494d2af230dce54d6b31c6af8fc24091316 /core/java/android/syncml | |
| download | frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2 | |
Initial Contribution
Diffstat (limited to 'core/java/android/syncml')
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 | 
