VCardParserImpl_V21.java revision 56650608f09fc75f260c03e00456ef3d1e60c929
14199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa/*
24199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * Copyright (C) 2010 The Android Open Source Project
34199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa *
44199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * Licensed under the Apache License, Version 2.0 (the "License");
54199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * you may not use this file except in compliance with the License.
64199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * You may obtain a copy of the License at
74199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa *
84199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa *      http://www.apache.org/licenses/LICENSE-2.0
94199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa *
104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * Unless required by applicable law or agreed to in writing, software
114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * distributed under the License is distributed on an "AS IS" BASIS,
124199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * See the License for the specific language governing permissions and
144199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * limitations under the License.
154199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa */
164199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawapackage com.android.vcard;
174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport com.android.vcard.exception.VCardAgentNotSupportedException;
194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport com.android.vcard.exception.VCardException;
204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport com.android.vcard.exception.VCardInvalidCommentLineException;
214199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport com.android.vcard.exception.VCardInvalidLineException;
224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport com.android.vcard.exception.VCardVersionException;
234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2448dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawaimport android.text.TextUtils;
251de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawaimport android.util.Base64;
2648dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawaimport android.util.Log;
2748dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa
284199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.io.BufferedReader;
294199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.io.IOException;
304199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.io.InputStream;
314199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.io.InputStreamReader;
324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.io.Reader;
334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.ArrayList;
341de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawaimport java.util.Collection;
354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.HashSet;
364560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawaimport java.util.List;
374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.Set;
384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa/**
404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * <p>
41677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa * Basic implementation achieving vCard parsing. Based on vCard 2.1.
424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * </p>
434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * @hide
444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa */
454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa/* package */ class VCardParserImpl_V21 {
4602117b3d19787ff65486b9f9db8abd338ae4c9f9Daisuke Miyakawa    private static final String LOG_TAG = VCardConstants.LOG_TAG;
474199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
48147f1ae5371954ae845cb2330b221df6ca1d8831Daisuke Miyakawa    protected static final class CustomBufferedReader extends BufferedReader {
494199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        private long mTime;
504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
51f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa        /**
52f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa         * Needed since "next line" may be null due to end of line.
53f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa         */
54f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa        private boolean mNextLineIsValid;
55f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa        private String mNextLine;
56f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa
574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        public CustomBufferedReader(Reader in) {
584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            super(in);
594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        @Override
624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        public String readLine() throws IOException {
63f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            if (mNextLineIsValid) {
64f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                final String ret = mNextLine;
65f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                mNextLine = null;
66f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                mNextLineIsValid = false;
67f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                return ret;
68f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            }
69f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa
70677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa            final long start = System.currentTimeMillis();
71f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            final String line = super.readLine();
72677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa            final long end = System.currentTimeMillis();
734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            mTime += end - start;
74f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            return line;
75f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa        }
76f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa
77f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa        /**
78f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa         * Read one line, but make this object store it in its queue.
79f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa         */
80f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa        public String peekLine() throws IOException {
81f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            if (!mNextLineIsValid) {
82677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa                final long start = System.currentTimeMillis();
83f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                final String line = super.readLine();
84677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa                final long end = System.currentTimeMillis();
85f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                mTime += end - start;
86f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa
87f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                mNextLine = line;
88f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                mNextLineIsValid = true;
89f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            }
90f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa
91f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            return mNextLine;
924199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
934199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
944199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        public long getTotalmillisecond() {
954199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return mTime;
964199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
974199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
984199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
994199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static final String DEFAULT_ENCODING = "8BIT";
1001de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    private static final String DEFAULT_CHARSET = "UTF-8";
1014199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1024199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected final String mIntermediateCharset;
1034199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1041de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    private final List<VCardInterpreter> mInterpreterList = new ArrayList<VCardInterpreter>();
1051de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    private boolean mCanceled;
10648dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa
1074199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
1084199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * <p>
1094199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * The encoding type for deconding byte streams. This member variable is
1104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * reset to a default encoding every time when a new item comes.
1114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
1124199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * <p>
1134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * "Encoding" in vCard is different from "Charset". It is mainly used for
1144199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * addresses, notes, images. "7BIT", "8BIT", "BASE64", and
1154199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * "QUOTED-PRINTABLE" are known examples.
1164199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
1174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
1184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected String mCurrentEncoding;
1194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1201de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    protected String mCurrentCharset;
1211de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa
1224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
1234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * <p>
1244199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * The reader object to be used internally.
1254199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
1264199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * <p>
1274199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * Developers should not directly read a line from this object. Use
1284199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * getLine() unless there some reason.
1294199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
1304199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
131f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa    protected CustomBufferedReader mReader;
1324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
1344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * <p>
1354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * Set for storing unkonwn TYPE attributes, which is not acceptable in vCard
1364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * specification, but happens to be seen in real world vCard.
1374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
138677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa     * <p>
139677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa     * We just accept those invalid types after emitting a warning for each of it.
140677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa     * </p>
1414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
1424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected final Set<String> mUnknownTypeSet = new HashSet<String>();
1434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
1454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * <p>
1464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * Set for storing unkonwn VALUE attributes, which is not acceptable in
1474199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * vCard specification, but happens to be seen in real world vCard.
1484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
149677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa     * <p>
150677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa     * We just accept those invalid types after emitting a warning for each of it.
151677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa     * </p>
1524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
1534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected final Set<String> mUnknownValueSet = new HashSet<String>();
1544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public VCardParserImpl_V21() {
1574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        this(VCardConfig.VCARD_TYPE_DEFAULT);
1584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
1594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    public VCardParserImpl_V21(int vcardType) {
1614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        mIntermediateCharset =  VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
1624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
1634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1644199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
1654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @return true when a given property name is a valid property name.
1664199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
1674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected boolean isValidPropertyName(final String propertyName) {
1684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (!(getKnownPropertyNameSet().contains(propertyName.toUpperCase()) ||
1694199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                propertyName.startsWith("X-"))
1704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                && !mUnknownTypeSet.contains(propertyName)) {
1714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            mUnknownTypeSet.add(propertyName);
1724199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
1734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
1744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return true;
1754199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
1764199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1774199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
1784199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @return String. It may be null, or its length may be 0
1794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @throws IOException
1804199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
1814199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected String getLine() throws IOException {
1824199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return mReader.readLine();
1834199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
1844199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
185f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa    protected String peekLine() throws IOException {
186f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa        return mReader.peekLine();
187f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa    }
188f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa
1894199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
1904199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @return String with it's length > 0
1914199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @throws IOException
1924199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @throws VCardException when the stream reached end of line
1934199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
1944199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected String getNonEmptyLine() throws IOException, VCardException {
1954199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        String line;
1964199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        while (true) {
1974199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            line = getLine();
1984199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (line == null) {
1994199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                throw new VCardException("Reached end of buffer.");
2004199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else if (line.trim().length() > 0) {
2014199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                return line;
2024199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
2034199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
2044199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
2054199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
20648dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa    /**
20748dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa     * <code>
2084199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
2094199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     *         items *CRLF
2104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     *         "END" [ws] ":" [ws] "VCARD"
21148dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa     * </code>
21256650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa     * @return False when reaching end of file.
2134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
21448dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa    private boolean parseOneVCard() throws IOException, VCardException {
21548dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        // reset for this entire vCard.
21648dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        mCurrentEncoding = DEFAULT_ENCODING;
2171de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        mCurrentCharset = DEFAULT_CHARSET;
2184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
21948dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        boolean allowGarbage = false;
2204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (!readBeginVCard(allowGarbage)) {
2214199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return false;
2224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
2231de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        for (VCardInterpreter interpreter : mInterpreterList) {
2241de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            interpreter.onEntryStarted();
2251de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        }
2264199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        parseItems();
2271de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        for (VCardInterpreter interpreter : mInterpreterList) {
2281de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            interpreter.onEntryEnded();
2291de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        }
2304199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return true;
2314199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
2324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
2344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @return True when successful. False when reaching the end of line
2354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @throws IOException
2364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @throws VCardException
2374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
2384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
23948dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        // TODO: use consructPropertyLine().
2404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        String line;
2414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        do {
2424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            while (true) {
2434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                line = getLine();
2444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (line == null) {
2454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    return false;
2464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                } else if (line.trim().length() > 0) {
2474199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    break;
2484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
2494199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
250f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            final String[] strArray = line.split(":", 2);
251f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            final int length = strArray.length;
2524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
253f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // Although vCard 2.1/3.0 specification does not allow lower cases,
254f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // we found vCard file emitted by some external vCard expoter have such
2554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            // invalid Strings.
25648dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            // e.g. BEGIN:vCard
2574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (length == 2 && strArray[0].trim().equalsIgnoreCase("BEGIN")
2584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    && strArray[1].trim().equalsIgnoreCase("VCARD")) {
2594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                return true;
2604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else if (!allowGarbage) {
26148dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                throw new VCardException("Expected String \"BEGIN:VCARD\" did not come "
26248dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                        + "(Instead, \"" + line + "\" came)");
2634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
2644199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } while (allowGarbage);
2654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2664199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        throw new VCardException("Reached where must not be reached.");
2674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
2684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2694199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
27048dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa     * Parses lines other than the first "BEGIN:VCARD". Takes care of "END:VCARD"n and
27148dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa     * "BEGIN:VCARD" in nested vCard.
2724199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
2734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /*
2744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * items = *CRLF item / item
27548dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa     *
27648dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa     * Note: BEGIN/END aren't include in the original spec while this method handles them.
2774199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
2784199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected void parseItems() throws IOException, VCardException {
2794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        boolean ended = false;
2804199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
28148dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        try {
28248dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            ended = parseItem();
28348dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        } catch (VCardInvalidCommentLineException e) {
28448dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
2854199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
2864199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2874199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        while (!ended) {
2884199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            try {
2894199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                ended = parseItem();
2904199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } catch (VCardInvalidCommentLineException e) {
2914199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
2924199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
2934199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
2944199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
2954199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2964199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /*
2974199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * item = [groups "."] name [params] ":" value CRLF / [groups "."] "ADR"
2984199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * [params] ":" addressparts CRLF / [groups "."] "ORG" [params] ":" orgparts
2994199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * CRLF / [groups "."] "N" [params] ":" nameparts CRLF / [groups "."]
3004199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * "AGENT" [params] ":" vcard CRLF
3014199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
3024199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected boolean parseItem() throws IOException, VCardException {
30348dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        // Reset for an item.
3044199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        mCurrentEncoding = DEFAULT_ENCODING;
3054199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
3064199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final String line = getNonEmptyLine();
3071de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        final VCardProperty propertyData = constructPropertyData(line);
3084199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
30948dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        final String propertyNameUpper = propertyData.getName().toUpperCase();
3101de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        final String propertyRawValue = propertyData.getRawValue();
31148dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa
31248dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        if (propertyNameUpper.equals(VCardConstants.PROPERTY_BEGIN)) {
3131de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            if (propertyRawValue.equalsIgnoreCase("VCARD")) {
31448dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                handleNest();
31548dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            } else {
3161de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                throw new VCardException("Unknown BEGIN type: " + propertyRawValue);
31748dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            }
31848dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        } else if (propertyNameUpper.equals(VCardConstants.PROPERTY_END)) {
3191de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            if (propertyRawValue.equalsIgnoreCase("VCARD")) {
32048dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                return true;  // Ended.
32148dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            } else {
3221de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                throw new VCardException("Unknown END type: " + propertyRawValue);
32348dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            }
32448dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        } else {
3251de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            parseItemInter(propertyData, propertyNameUpper);
3264199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
32748dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        return false;
32848dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa    }
32948dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa
3301de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    private void parseItemInter(VCardProperty property, String propertyNameUpper)
33148dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            throws IOException, VCardException {
3321de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        String propertyRawValue = property.getRawValue();
3331de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        if (propertyNameUpper.equals(VCardConstants.PROPERTY_AGENT)) {
3341de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            handleAgent(property);
33548dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        } else if (isValidPropertyName(propertyNameUpper)) {
33648dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            if (propertyNameUpper.equals(VCardConstants.PROPERTY_VERSION) &&
3371de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                    !propertyRawValue.equals(getVersionString())) {
3381de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                throw new VCardVersionException(
3391de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                        "Incompatible version: " + propertyRawValue + " != " + getVersionString());
3404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
3411de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            handlePropertyValue(property, propertyNameUpper);
34248dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        } else {
34348dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            throw new VCardException("Unknown property name: \"" + propertyNameUpper + "\"");
3444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
34548dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa    }
3464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
34748dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa    private void handleNest() throws IOException, VCardException {
3481de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        for (VCardInterpreter interpreter : mInterpreterList) {
3491de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            interpreter.onEntryStarted();
3501de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        }
35148dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        parseItems();
3521de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        for (VCardInterpreter interpreter : mInterpreterList) {
3531de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            interpreter.onEntryEnded();
3541de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        }
3554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
3564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
3574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    // For performance reason, the states for group and property name are merged into one.
3584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    static private final int STATE_GROUP_OR_PROPERTY_NAME = 0;
3594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    static private final int STATE_PARAMS = 1;
3604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    // vCard 3.0 specification allows double-quoted parameters, while vCard 2.1 does not.
3614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    static private final int STATE_PARAMS_IN_DQUOTE = 2;
3624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
3631de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    protected VCardProperty constructPropertyData(String line) throws VCardException {
3641de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        final VCardProperty propertyData = new VCardProperty();
36548dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa
3664199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final int length = line.length();
3674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (length > 0 && line.charAt(0) == '#') {
3684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            throw new VCardInvalidCommentLineException();
3694199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
3704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
3714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        int state = STATE_GROUP_OR_PROPERTY_NAME;
3724199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        int nameIndex = 0;
3734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
3744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // This loop is developed so that we don't have to take care of bottle neck here.
3754199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // Refactor carefully when you need to do so.
3764199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        for (int i = 0; i < length; i++) {
3774199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final char ch = line.charAt(i);
3784199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            switch (state) {
3794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                case STATE_GROUP_OR_PROPERTY_NAME: {
3804199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    if (ch == ':') {  // End of a property name.
3814199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        final String propertyName = line.substring(nameIndex, i);
38248dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                        propertyData.setName(propertyName);
38348dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                        propertyData.setRawValue( i < length - 1 ? line.substring(i + 1) : "");
38448dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                        return propertyData;
3854199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    } else if (ch == '.') {  // Each group is followed by the dot.
3864199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        final String groupName = line.substring(nameIndex, i);
3874199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        if (groupName.length() == 0) {
3884199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                            Log.w(LOG_TAG, "Empty group found. Ignoring.");
3894560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa                        } else {
39048dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                            propertyData.addGroup(groupName);
3914199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        }
3924199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        nameIndex = i + 1;  // Next should be another group or a property name.
393677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa                    } else if (ch == ';') {  // End of property name and beginneng of parameters.
3944199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        final String propertyName = line.substring(nameIndex, i);
39548dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                        propertyData.setName(propertyName);
3964199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        nameIndex = i + 1;
3974199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        state = STATE_PARAMS;  // Start parameter parsing.
3984199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    }
399d5a8fc2a35c69fc34df35fd545ccf83d548ba50cDaisuke Miyakawa                    // TODO: comma support (in vCard 3.0 and 4.0).
4004199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    break;
4014199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
4024199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                case STATE_PARAMS: {
4034199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    if (ch == '"') {
4044199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
4054199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                            Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
4064199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                                    "Silently allow it");
4074199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        }
4084199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        state = STATE_PARAMS_IN_DQUOTE;
4094199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    } else if (ch == ';') {  // Starts another param.
41048dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                        handleParams(propertyData, line.substring(nameIndex, i));
4114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        nameIndex = i + 1;
4124199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    } else if (ch == ':') {  // End of param and beginenning of values.
41348dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                        handleParams(propertyData, line.substring(nameIndex, i));
41448dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                        propertyData.setRawValue(i < length - 1 ? line.substring(i + 1) : "");
41548dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                        return propertyData;
4164199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    }
4174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    break;
4184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
4194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                case STATE_PARAMS_IN_DQUOTE: {
4204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    if (ch == '"') {
4214199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
4224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                            Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
4234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                                    "Silently allow it");
4244199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        }
4254199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        state = STATE_PARAMS;
4264199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    }
4274199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    break;
4284199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
4294199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
4304199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
4314199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
4324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
4334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
4344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
4354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /*
4364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * params = ";" [ws] paramlist paramlist = paramlist [ws] ";" [ws] param /
4374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * param param = "TYPE" [ws] "=" [ws] ptypeval / "VALUE" [ws] "=" [ws]
4384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * pvalueval / "ENCODING" [ws] "=" [ws] pencodingval / "CHARSET" [ws] "="
4394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * [ws] charsetval / "LANGUAGE" [ws] "=" [ws] langval / "X-" word [ws] "="
4404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * [ws] word / knowntype
4414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
4421de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    protected void handleParams(VCardProperty propertyData, String params)
44348dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            throws VCardException {
4444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final String[] strArray = params.split("=", 2);
4454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (strArray.length == 2) {
4464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final String paramName = strArray[0].trim().toUpperCase();
4474199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            String paramValue = strArray[1].trim();
4484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (paramName.equals("TYPE")) {
44948dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                handleType(propertyData, paramValue);
4504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else if (paramName.equals("VALUE")) {
45148dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                handleValue(propertyData, paramValue);
4524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else if (paramName.equals("ENCODING")) {
45348dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                handleEncoding(propertyData, paramValue);
4544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else if (paramName.equals("CHARSET")) {
45548dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                handleCharset(propertyData, paramValue);
4564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else if (paramName.equals("LANGUAGE")) {
45748dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                handleLanguage(propertyData, paramValue);
4584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else if (paramName.startsWith("X-")) {
45948dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa                handleAnyParam(propertyData, paramName, paramValue);
4604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else {
4614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                throw new VCardException("Unknown type \"" + paramName + "\"");
4624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
4634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } else {
46448dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            handleParamWithoutName(propertyData, strArray[0]);
4654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
4664199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
4674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
4684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
4694199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * vCard 3.0 parser implementation may throw VCardException.
4704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
4711de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    protected void handleParamWithoutName(VCardProperty propertyData, final String paramValue) {
47248dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa        handleType(propertyData, paramValue);
4734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
4744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
4754199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /*
4764199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * ptypeval = knowntype / "X-" word
4774199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
4781de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    protected void handleType(VCardProperty propertyData, final String ptypeval) {
4794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (!(getKnownTypeSet().contains(ptypeval.toUpperCase())
4804199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                || ptypeval.startsWith("X-"))
4814199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                && !mUnknownTypeSet.contains(ptypeval)) {
4824199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            mUnknownTypeSet.add(ptypeval);
4834199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), ptypeval));
4844199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
4851de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        propertyData.addParameter(VCardConstants.PARAM_TYPE, ptypeval);
4864199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
4874199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
4884199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /*
4894199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
4904199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
4911de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    protected void handleValue(VCardProperty propertyData, final String pvalueval) {
4924199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (!(getKnownValueSet().contains(pvalueval.toUpperCase())
4934199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                || pvalueval.startsWith("X-")
4944199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                || mUnknownValueSet.contains(pvalueval))) {
4954199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            mUnknownValueSet.add(pvalueval);
4964199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            Log.w(LOG_TAG, String.format(
4974199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    "The value unsupported by TYPE of %s: ", getVersion(), pvalueval));
4984199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
4991de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        propertyData.addParameter(VCardConstants.PARAM_VALUE, pvalueval);
5004199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
5014199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
5024199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /*
5034199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
5044199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
5051de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    protected void handleEncoding(VCardProperty propertyData, String pencodingval)
50648dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            throws VCardException {
5074199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (getAvailableEncodingSet().contains(pencodingval) ||
5084199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                pencodingval.startsWith("X-")) {
5091de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            propertyData.addParameter(VCardConstants.PARAM_ENCODING, pencodingval);
51048dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            // Update encoding right away, as this is needed to understanding other params.
5114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            mCurrentEncoding = pencodingval;
5124199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } else {
5134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
5144199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
5154199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
5164199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
5174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
5184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * <p>
5194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
5204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * but recent vCard files often contain other charset like UTF-8, SHIFT_JIS, etc.
5214199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * We allow any charset.
5224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
5234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
5241de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    protected void handleCharset(VCardProperty propertyData, String charsetval) {
5251de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        mCurrentCharset = charsetval;
5261de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        propertyData.addParameter(VCardConstants.PARAM_CHARSET, charsetval);
5274199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
5284199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
5294199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
5304199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * See also Section 7.1 of RFC 1521
5314199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
5321de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    protected void handleLanguage(VCardProperty propertyData, String langval)
53348dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa            throws VCardException {
5344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        String[] strArray = langval.split("-");
5354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (strArray.length != 2) {
5364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            throw new VCardException("Invalid Language: \"" + langval + "\"");
5374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
5384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        String tmp = strArray[0];
5394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        int length = tmp.length();
5404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        for (int i = 0; i < length; i++) {
5414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (!isAsciiLetter(tmp.charAt(i))) {
5424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                throw new VCardException("Invalid Language: \"" + langval + "\"");
5434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
5444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
5454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        tmp = strArray[1];
5464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        length = tmp.length();
5474199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        for (int i = 0; i < length; i++) {
5484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (!isAsciiLetter(tmp.charAt(i))) {
5494199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                throw new VCardException("Invalid Language: \"" + langval + "\"");
5504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
5514199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
5521de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        propertyData.addParameter(VCardConstants.PARAM_LANGUAGE, langval);
5534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
5544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
5554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private boolean isAsciiLetter(char ch) {
5564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
5574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return true;
5584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
5594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return false;
5604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
5614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
5624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
5634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * Mainly for "X-" type. This accepts any kind of type without check.
5644199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
56548dd8e86a81d2ab40eb762975c8211c225002bf0Daisuke Miyakawa    protected void handleAnyParam(
5661de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            VCardProperty propertyData, String paramName, String paramValue) {
5671de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        propertyData.addParameter(paramName, paramValue);
5684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
5694199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
5701de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    protected void handlePropertyValue(VCardProperty property, String propertyName)
5714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            throws IOException, VCardException {
5721de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        final String propertyNameUpper = property.getName().toUpperCase();
5731de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        String propertyRawValue = property.getRawValue();
5741de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        final String sourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
5751de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        final Collection<String> charsetCollection =
5761de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                property.getParameters(VCardConstants.PARAM_CHARSET);
5771de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        String targetCharset =
5781de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                ((charsetCollection != null) ? charsetCollection.iterator().next() : null);
5791de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        if (TextUtils.isEmpty(targetCharset)) {
5801de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            targetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
5811de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        }
5821de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa
5831de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        // TODO: have "separableProperty" which reflects vCard spec..
5841de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        if (propertyNameUpper.equals(VCardConstants.PROPERTY_ADR)
5851de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                || propertyNameUpper.equals(VCardConstants.PROPERTY_ORG)
5861de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                || propertyNameUpper.equals(VCardConstants.PROPERTY_N)) {
5871de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            List<String> encodedValueList = new ArrayList<String>();
5881de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa
5891de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some softwares/devices emit
5901de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            // such data.
5911de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            if (mCurrentEncoding.equalsIgnoreCase(VCardConstants.PARAM_ENCODING_QP)) {
5921de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                // First we retrieve Quoted-Printable String from vCard entry, which may include
5931de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                // multiple lines.
5941de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                final String quotedPrintablePart = getQuotedPrintablePart(propertyRawValue);
5951de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa
5961de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                // "Raw value" from the view of users should contain all part of QP string.
5971de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                // TODO: add test for this handling
5981de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                property.setRawValue(quotedPrintablePart);
5991de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa
6001de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                // We split Quoted-Printable String using semi-colon before decoding it, as
6011de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                // the Quoted-Printable may have semi-colon, which confuses splitter.
6021de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                final List<String> quotedPrintableValueList =
6031de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                        VCardUtils.constructListFromValue(quotedPrintablePart, getVersion());
6041de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                for (String quotedPrintableValue : quotedPrintableValueList) {
6051de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                    String encoded = VCardUtils.parseQuotedPrintable(quotedPrintableValue,
6061de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                            false, sourceCharset, targetCharset);
6071de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                    encodedValueList.add(encoded);
6081de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                }
6091de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            } else {
6101de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                final List<String> rawValueList =
6111de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                    VCardUtils.constructListFromValue(propertyRawValue, getVersion());
6121de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                for (String rawValue : rawValueList) {
6131de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                    encodedValueList.add(VCardUtils.convertStringCharset(
6141de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                            rawValue, sourceCharset, targetCharset));
6151de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                }
6161de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            }
6171de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa
6181de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            property.setValues(encodedValueList);
6191de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            for (VCardInterpreter interpreter : mInterpreterList) {
6201de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                interpreter.onPropertyCreated(property);
6211de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            }
6221de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            return;
6231de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        }
6241de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa
6254199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final String upperEncoding = mCurrentEncoding.toUpperCase();
6264199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_QP)) {
6271de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            final String quotedPrintablePart = getQuotedPrintablePart(propertyRawValue);
6281de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            final String propertyEncodedValue =
6291de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                    VCardUtils.parseQuotedPrintable(quotedPrintablePart,
6301de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                            false, sourceCharset, targetCharset);
6311de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            property.setRawValue(quotedPrintablePart);
6321de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            property.setValues(propertyEncodedValue);
6331de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            for (VCardInterpreter interpreter : mInterpreterList) {
6341de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                interpreter.onPropertyCreated(property);
6351de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            }
6364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } else if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_BASE64)
6374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                || upperEncoding.equals(VCardConstants.PARAM_ENCODING_B)) {
6384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            // It is very rare, but some BASE64 data may be so big that
6394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            // OutOfMemoryError occurs. To ignore such cases, use try-catch.
6404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            try {
6411de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                property.setByteValue(Base64.decode(getBase64(propertyRawValue), Base64.DEFAULT));
6421de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                for (VCardInterpreter interpreter : mInterpreterList) {
6431de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                    interpreter.onPropertyCreated(property);
6441de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                }
6454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } catch (OutOfMemoryError error) {
6464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
6471de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                for (VCardInterpreter interpreter : mInterpreterList) {
6481de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                    interpreter.onPropertyCreated(property);
6491de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                }
6504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
6514199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } else {
6524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (!(upperEncoding.equals("7BIT") || upperEncoding.equals("8BIT") ||
6534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    upperEncoding.startsWith("X-"))) {
6544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                Log.w(LOG_TAG,
6554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        String.format("The encoding \"%s\" is unsupported by vCard %s",
6564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                                mCurrentEncoding, getVersionString()));
6574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
6584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
659f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // Some device uses line folding defined in RFC 2425, which is not allowed
660f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // in vCard 2.1 (while needed in vCard 3.0).
661f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            //
662f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // e.g.
663f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // BEGIN:VCARD
664f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // VERSION:2.1
665f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // N:;Omega;;;
666f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // EMAIL;INTERNET:"Omega"
667f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            //   <omega@example.com>
668f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // FN:Omega
669f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // END:VCARD
670f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            //
671f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // The vCard above assumes that email address should become:
672f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // "Omega" <omega@example.com>
673f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            //
674f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // But vCard 2.1 requires Quote-Printable when a line contains line break(s).
675f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            //
676f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // For more information about line folding,
677f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // see "5.8.1. Line delimiting and folding" in RFC 2425.
678f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            //
679f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // We take care of this case more formally in vCard 3.0, so we only need to
680f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            // do this in vCard 2.1.
681be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa            if (getVersion() == VCardConfig.VERSION_21) {
682f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                StringBuilder builder = null;
683f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                while (true) {
684f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                    final String nextLine = peekLine();
685f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                    // We don't need to care too much about this exceptional case,
686f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                    // but we should not wrongly eat up "END:VCARD", since it critically
687f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                    // breaks this parser's state machine.
688f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                    // Thus we roughly look over the next line and confirm it is at least not
689f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                    // "END:VCARD". This extra fee is worth paying. This is exceptional
690f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                    // anyway.
691f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                    if (!TextUtils.isEmpty(nextLine) &&
692f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                            nextLine.charAt(0) == ' ' &&
693f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                            !"END:VCARD".contains(nextLine.toUpperCase())) {
694f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                        getLine();  // Drop the next line.
695f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa
696f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                        if (builder == null) {
697f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                            builder = new StringBuilder();
6981de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                            builder.append(propertyRawValue);
699f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                        }
700f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                        builder.append(nextLine.substring(1));
701f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                    } else {
702f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                        break;
703f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                    }
704f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                }
705f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                if (builder != null) {
7061de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                    propertyRawValue = builder.toString();
707f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa                }
708f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa            }
709f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa
7101de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            ArrayList<String> propertyValueList = new ArrayList<String>();
7111de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            String value = VCardUtils.convertStringCharset(
7121de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                    maybeUnescapeText(propertyRawValue), sourceCharset, targetCharset);
7131de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            propertyValueList.add(value);
7141de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            property.setValues(propertyValueList);
7151de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            for (VCardInterpreter interpreter : mInterpreterList) {
7161de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                interpreter.onPropertyCreated(property);
7171de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            }
7184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
7194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
7204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
7214199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
7224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * <p>
7234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * Parses and returns Quoted-Printable.
7244199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * </p>
7254199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     *
7264199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @param firstString The string following a parameter name and attributes.
7274199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     *            Example: "string" in
7284199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     *            "ADR:ENCODING=QUOTED-PRINTABLE:string\n\r".
7294199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @return whole Quoted-Printable string, including a given argument and
7304199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     *         following lines. Excludes the last empty line following to Quoted
7314199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     *         Printable lines.
7324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @throws IOException
7334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * @throws VCardException
7344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
7351de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    private String getQuotedPrintablePart(String firstString)
7361de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            throws IOException, VCardException {
7374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // Specifically, there may be some padding between = and CRLF.
7384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // See the following:
7394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        //
7404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // qp-line := *(qp-segment transport-padding CRLF)
7414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // qp-part transport-padding
7424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // qp-segment := qp-section *(SPACE / TAB) "="
7434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // ; Maximum length of 76 characters
7444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        //
7454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // e.g. (from RFC 2045)
7464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // Now's the time =
7474199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // for all folk to come=
7484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // to the aid of their country.
7494199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (firstString.trim().endsWith("=")) {
7504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            // remove "transport-padding"
7514199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            int pos = firstString.length() - 1;
7524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            while (firstString.charAt(pos) != '=') {
7534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
7544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            StringBuilder builder = new StringBuilder();
7554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            builder.append(firstString.substring(0, pos + 1));
7564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            builder.append("\r\n");
7574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            String line;
7584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            while (true) {
7594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                line = getLine();
7604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (line == null) {
7614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    throw new VCardException("File ended during parsing a Quoted-Printable String");
7624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
7634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (line.trim().endsWith("=")) {
7644199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    // remove "transport-padding"
7654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    pos = line.length() - 1;
7664199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    while (line.charAt(pos) != '=') {
7674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    }
7684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    builder.append(line.substring(0, pos + 1));
7694199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    builder.append("\r\n");
7704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                } else {
7714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    builder.append(line);
7724199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    break;
7734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
7744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
7754199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return builder.toString();
7764199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } else {
7774199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return firstString;
7784199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
7794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
7804199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
7814199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected String getBase64(String firstString) throws IOException, VCardException {
782c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa        final StringBuilder builder = new StringBuilder();
7834199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        builder.append(firstString);
7844199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
7854199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        while (true) {
786c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa            final String line = peekLine();
7874199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (line == null) {
7884199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                throw new VCardException("File ended during parsing BASE64 binary");
7894199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
790c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa
791c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa            // vCard 2.1 requires two spaces at the end of BASE64 strings, but some vCard doesn't
792c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa            // have them. We try to detect those cases using semi-colon, given BASE64 doesn't
793c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa            // contain it. Specifically BASE64 doesn't have semi-colon in it, so we should be able
794c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa            // to detect the case safely.
795c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa            if (line.contains(":")) {
796c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa                if (getKnownPropertyNameSet().contains(
797c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa                        line.substring(0, line.indexOf(":")).toUpperCase())) {
798c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa                    Log.w(LOG_TAG, "Found a next property during parsing a BASE64 string, " +
799c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa                            "which must not contain semi-colon. Treat the line as next property.");
800c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa                    Log.w(LOG_TAG, "Problematic line: " + line.trim());
801c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa                    break;
802c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa                }
803c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa            }
804c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa
805c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa            // Consume the line.
806c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa            getLine();
807c955c8b0da0c9fcbad0ddcae76641358c27e72cdDaisuke Miyakawa
8084199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (line.length() == 0) {
8094199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                break;
8104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
8114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            builder.append(line);
8124199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
8134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8144199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return builder.toString();
8154199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8164199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /*
8184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * vCard 2.1 specifies AGENT allows one vcard entry. Currently we emit an
8194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * error toward the AGENT property.
8204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * // TODO: Support AGENT property.
8214199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * item =
8224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * ... / [groups "."] "AGENT" [params] ":" vcard CRLF vcard = "BEGIN" [ws]
8234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":" [ws] "VCARD"
8244199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
8251de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    protected void handleAgent(final VCardProperty property) throws VCardException {
8261de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        if (!property.getRawValue().toUpperCase().contains("BEGIN:VCARD")) {
8274199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            // Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
8281de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            for (VCardInterpreter interpreter : mInterpreterList) {
8291de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa                interpreter.onPropertyCreated(property);
8301de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            }
8314199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return;
8324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } else {
8334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
8344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
8354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
8384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * For vCard 3.0.
8394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
8404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected String maybeUnescapeText(final String text) {
8414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return text;
8424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
8454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * Returns unescaped String if the character should be unescaped. Return
8464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * null otherwise. e.g. In vCard 2.1, "\;" should be unescaped into ";"
8474199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * while "\x" should not be.
8484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
8494560bdde6dd75cca49fc55b58aafb5d416b88ca3Daisuke Miyakawa    protected String maybeUnescapeCharacter(final char ch) {
8504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return unescapeCharacter(ch);
8514199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /* package */ static String unescapeCharacter(final char ch) {
8544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // Original vCard 2.1 specification does not allow transformation
8554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous
8564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // implementation of
8574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // this class allowed them, so keep it as is.
8584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
8594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return String.valueOf(ch);
8604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        } else {
8614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            return null;
8624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
8634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8644199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
866be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa     * @return {@link VCardConfig#VERSION_21}
8674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
8684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected int getVersion() {
869be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        return VCardConfig.VERSION_21;
8704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8724199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /**
873be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa     * @return {@link VCardConfig#VERSION_30}
8744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     */
8754199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected String getVersionString() {
8764199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return VCardConstants.VERSION_V21;
8774199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8784199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected Set<String> getKnownPropertyNameSet() {
8804199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return VCardParser_V21.sKnownPropertyNameSet;
8814199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8824199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8834199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected Set<String> getKnownTypeSet() {
8844199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return VCardParser_V21.sKnownTypeSet;
8854199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8864199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8874199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected Set<String> getKnownValueSet() {
8884199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return VCardParser_V21.sKnownValueSet;
8894199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8904199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8914199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected Set<String> getAvailableEncodingSet() {
8924199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return VCardParser_V21.sAvailableEncoding;
8934199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8944199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8954199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    protected String getDefaultEncoding() {
8964199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return DEFAULT_ENCODING;
8974199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
8984199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
8991de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    protected String getDefaultCharset() {
9001de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        return DEFAULT_CHARSET;
9011de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    }
9024199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
9031de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    protected String getCurrentCharset() {
9041de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        return mCurrentCharset;
9051de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    }
9061de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa
9071de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    public void addInterpreter(VCardInterpreter interpreter) {
9081de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        mInterpreterList.add(interpreter);
9091de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    }
9101de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa
9111de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    public void parse(InputStream is) throws IOException, VCardException {
9124199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (is == null) {
9134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            throw new NullPointerException("InputStream must not be null.");
9144199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
9154199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
9164199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final InputStreamReader tmpReader = new InputStreamReader(is, mIntermediateCharset);
917f6d9e0eeae38a72481ce2e19d0872d3f8f81189fDaisuke Miyakawa        mReader = new CustomBufferedReader(tmpReader);
9184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
9194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final long start = System.currentTimeMillis();
9201de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        for (VCardInterpreter interpreter : mInterpreterList) {
9211de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            interpreter.onVCardStarted();
9224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
92356650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa
92456650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa        // vcard_file = [wsls] vcard [wsls]
92556650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa        while (true) {
92656650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa            synchronized (this) {
92756650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa                if (mCanceled) {
92856650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa                    Log.i(LOG_TAG, "Cancel request has come. exitting parse operation.");
92956650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa                    break;
93056650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa                }
93156650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa            }
93256650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa            if (!parseOneVCard()) {
93356650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa                break;
93456650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa            }
93556650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa        }
93656650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa
93756650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa        for (VCardInterpreter interpreter : mInterpreterList) {
93856650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa            interpreter.onVCardEnded();
93956650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa        }
94056650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa    }
94156650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa
94256650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa    public void parseOne(InputStream is) throws IOException, VCardException {
94356650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa        if (is == null) {
94456650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa            throw new NullPointerException("InputStream must not be null.");
94556650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa        }
94656650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa
94756650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa        final InputStreamReader tmpReader = new InputStreamReader(is, mIntermediateCharset);
94856650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa        mReader = new CustomBufferedReader(tmpReader);
94956650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa
95056650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa        final long start = System.currentTimeMillis();
95156650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa        for (VCardInterpreter interpreter : mInterpreterList) {
95256650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa            interpreter.onVCardStarted();
95356650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa        }
95456650608f09fc75f260c03e00456ef3d1e60c929Daisuke Miyakawa        parseOneVCard();
9551de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa        for (VCardInterpreter interpreter : mInterpreterList) {
9561de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa            interpreter.onVCardEnded();
9574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
9584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
9594199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
9601de396f6df89363169d3a2e61a61fa98d12c1ef8Daisuke Miyakawa    public final synchronized void cancel() {
961b23043e3a199e951817b6d86c301b9521d4c26feDaisuke Miyakawa        Log.i(LOG_TAG, "ParserImpl received cancel operation.");
9624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        mCanceled = true;
9634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
9644199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa}
965