Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 58b50f86 authored by Daisuke Miyakawa's avatar Daisuke Miyakawa
Browse files

Refactor vCard parser so that its implementation detail would be hidden from users.

Move the current implementation or 2.1 and 3.0 to VCardParserImpl_V21 and VCardParserImpl_V30.
- From the view of users, it is strange to make vCard 3.0 inherit vCard 2.1.
- Those two specifications still have similar implementation detail, so make implementation of vCard 3.0 inherits vCard 2.1 makes sense from technical view point.

Now VCardParser_V21 and VCardParser_V30 are independent class.

Remove strict mode in vCard 3.0, since strict parsing is not necessary from the view of actual use, and needs to extra effort to support it.

Bug: 2576738
Change-Id: I1d5cb6cffffb53337faa2dd7c519e7254e12da86
parent 40e8b1de
Loading
Loading
Loading
Loading
+23 −33
Original line number Diff line number Diff line
@@ -20,47 +20,34 @@ import android.pim.vcard.exception.VCardException;
import java.io.IOException;
import java.io.InputStream;

public abstract class VCardParser {
    protected final int mParseType;
    protected boolean mCanceled;

    public VCardParser() {
        this(VCardConfig.PARSE_TYPE_UNKNOWN);
    }

    public VCardParser(int parseType) {
        mParseType = parseType;
    }

public interface VCardParser {
    /**
     * <P>
     * <p>
     * Parses the given stream and send the vCard data into VCardBuilderBase object.
     * </P.
     * <P>
     * </p>.
     * <p>
     * Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets
     * local encoding to it. For example, Japanese phone career uses Shift_JIS, which is
     * formally allowed in vCard 2.1, but not allowed in vCard 3.0. In vCard 2.1,
     * In some exreme case, it is allowed for vCard to have different charsets in one vCard.
     * </P>
     * <P>
     * </p>
     * <p>
     * We recommend you use {@link VCardSourceDetector} and detect which kind of source the
     * vCard comes from and explicitly specify a charset using the result.
     * </P>
     * </p>
     *
     * @param is The source to parse.
     * @param interepreter A {@link VCardInterpreter} object which used to construct data.
     * @return Returns true for success. Otherwise returns false.
     * @throws IOException, VCardException
     */
    public final boolean parse(InputStream is, VCardInterpreter interepreter)
            throws IOException, VCardException {
        return parse(is, VCardConfig.DEFAULT_TEMPORARY_CHARSET, interepreter);
    }
    public boolean parse(InputStream is, VCardInterpreter interepreter)
            throws IOException, VCardException;

    /**
     * <P>
     * <p>
     * The method variants which accept charset.
     * </P>
     * </p>
     *
     * @param is The source to parse.
     * @param charset Charset to be used.
@@ -68,22 +55,25 @@ public abstract class VCardParser {
     * @return Returns true when successful. Otherwise returns false.
     * @throws IOException, VCardException
     */
    public abstract boolean parse(InputStream is, String charset,
            VCardInterpreter interpreter)
    public boolean parse(InputStream is, String charset, VCardInterpreter interpreter)
            throws IOException, VCardException;
    
    /**
     * The method variants which tells this object the operation is already canceled.
     * @hide
     */
    public abstract void parse(InputStream is, String charset,
    // TODO: remove this if possible.
    public boolean parse(InputStream is, String charset,
            VCardInterpreter builder, boolean canceled)
        throws IOException, VCardException;

    /**
     * Cancel parsing.
     * Actual cancel is done after the end of the current one vcard entry parsing.
     * <p>
     * Cancel parsing vCard. Useful when you want to stop the parse in the other threads.
     * </p>
     * <p>
     * Actual cancel is done after parsing the current vcard.
     * </p>
     */
    public void cancel() {
        mCanceled = true;
    }
    public abstract void cancel();
}
+996 −0

File added.

Preview size limit exceeded, changes collapsed.

+305 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.pim.vcard;

import java.io.IOException;
import java.util.Set;

import android.pim.vcard.exception.VCardException;
import android.util.Log;

/* package */ class VCardParserImpl_V30 extends VCardParserImpl_V21 {
    private static final String LOG_TAG = "VCardParserImpl_V30";

    private String mPreviousLine;

    private boolean mEmittedAgentWarning = false;

    public VCardParserImpl_V30() {
        super();
    }

    public VCardParserImpl_V30(VCardSourceDetector detector) {
        this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN);
    }

    public VCardParserImpl_V30(int parseMode) {
        super(parseMode);
    }

    @Override
    protected int getVersion() {
        return VCardConfig.FLAG_V30;
    }

    @Override
    protected String getVersionString() {
        return VCardConstants.VERSION_V30;
    }

    @Override
    protected String getLine() throws IOException {
        if (mPreviousLine != null) {
            String ret = mPreviousLine;
            mPreviousLine = null;
            return ret;
        } else {
            return mReader.readLine();
        }
    }

    /**
     * vCard 3.0 requires that the line with space at the beginning of the line
     * must be combined with previous line.
     */
    @Override
    protected String getNonEmptyLine() throws IOException, VCardException {
        String line;
        StringBuilder builder = null;
        while (true) {
            line = mReader.readLine();
            if (line == null) {
                if (builder != null) {
                    return builder.toString();
                } else if (mPreviousLine != null) {
                    String ret = mPreviousLine;
                    mPreviousLine = null;
                    return ret;
                }
                throw new VCardException("Reached end of buffer.");
            } else if (line.length() == 0) {
                if (builder != null) {
                    return builder.toString();
                } else if (mPreviousLine != null) {
                    String ret = mPreviousLine;
                    mPreviousLine = null;
                    return ret;
                }
            } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
                if (builder != null) {
                    // See Section 5.8.1 of RFC 2425 (MIME-DIR document).
                    // Following is the excerpts from it.
                    //
                    // DESCRIPTION:This is a long description that exists on a long line.
                    //
                    // Can be represented as:
                    //
                    // DESCRIPTION:This is a long description
                    //  that exists on a long line.
                    //
                    // It could also be represented as:
                    //
                    // DESCRIPTION:This is a long descrip
                    //  tion that exists o
                    //  n a long line.
                    builder.append(line.substring(1));
                } else if (mPreviousLine != null) {
                    builder = new StringBuilder();
                    builder.append(mPreviousLine);
                    mPreviousLine = null;
                    builder.append(line.substring(1));
                } else {
                    throw new VCardException("Space exists at the beginning of the line");
                }
            } else {
                if (mPreviousLine == null) {
                    mPreviousLine = line;
                    if (builder != null) {
                        return builder.toString();
                    }
                } else {
                    String ret = mPreviousLine;
                    mPreviousLine = line;
                    return ret;
                }
            }
        }
    }

    /*
     * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
     *         1 * (contentline)
     *         ;A vCard object MUST include the VERSION, FN and N types.
     *         [group "."] "END" ":" "VCARD" 1 * CRLF
     */
    @Override
    protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
        // TODO: vCard 3.0 supports group.
        return super.readBeginVCard(allowGarbage);
    }

    @Override
    protected void readEndVCard(boolean useCache, boolean allowGarbage)
            throws IOException, VCardException {
        // TODO: vCard 3.0 supports group.
        super.readEndVCard(useCache, allowGarbage);
    }

    /**
     * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
     */
    @Override
    protected void handleParams(final String params) throws VCardException {
        try {
            super.handleParams(params);
        } catch (VCardException e) {
            // maybe IANA type
            String[] strArray = params.split("=", 2);
            if (strArray.length == 2) {
                handleAnyParam(strArray[0], strArray[1]);
            } else {
                // Must not come here in the current implementation.
                throw new VCardException(
                        "Unknown params value: " + params);
            }
        }
    }

    @Override
    protected void handleAnyParam(final String paramName, final String paramValue) {
        super.handleAnyParam(paramName, paramValue);
    }

    @Override
    protected void handleParamWithoutName(final String paramValue) throws VCardException {
        super.handleParamWithoutName(paramValue);
    }

    /*
     *  vCard 3.0 defines
     *
     *  param         = param-name "=" param-value *("," param-value)
     *  param-name    = iana-token / x-name
     *  param-value   = ptext / quoted-string
     *  quoted-string = DQUOTE QSAFE-CHAR DQUOTE
     */
    @Override
    protected void handleType(final String ptypevalues) {
        String[] ptypeArray = ptypevalues.split(",");
        mInterpreter.propertyParamType("TYPE");
        for (String value : ptypeArray) {
            int length = value.length();
            if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
                mInterpreter.propertyParamValue(value.substring(1, value.length() - 1));
            } else {
                mInterpreter.propertyParamValue(value);
            }
        }
    }

    @Override
    protected void handleAgent(final String propertyValue) {
        // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
        //
        // e.g.
        // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
        //  TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
        //  ET:jfriday@host.com\nEND:VCARD\n
        //
        // TODO: fix this.
        //
        // issue:
        //  vCard 3.0 also allows this as an example.
        //
        // AGENT;VALUE=uri:
        //  CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
        //
        // This is not vCard. Should we support this?
        //
        // Just ignore the line for now, since we cannot know how to handle it...
        if (!mEmittedAgentWarning) {
            Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
            mEmittedAgentWarning = true;
        }
    }

    /**
     * vCard 3.0 does not require two CRLF at the last of BASE64 data.
     * It only requires that data should be MIME-encoded.
     */
    @Override
    protected String getBase64(final String firstString)
            throws IOException, VCardException {
        final StringBuilder builder = new StringBuilder();
        builder.append(firstString);

        while (true) {
            final String line = getLine();
            if (line == null) {
                throw new VCardException(
                        "File ended during parsing BASE64 binary");
            }
            if (line.length() == 0) {
                break;
            } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
                mPreviousLine = line;
                break;
            }
            builder.append(line);
        }

        return builder.toString();
    }

    /**
     * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
     *              ; \\ encodes \, \n or \N encodes newline
     *              ; \; encodes ;, \, encodes ,
     *
     * Note: Apple escapes ':' into '\:' while does not escape '\'
     */
    @Override
    protected String maybeUnescapeText(final String text) {
        return unescapeText(text);
    }

    public static String unescapeText(final String text) {
        StringBuilder builder = new StringBuilder();
        final int length = text.length();
        for (int i = 0; i < length; i++) {
            char ch = text.charAt(i);
            if (ch == '\\' && i < length - 1) {
                final char next_ch = text.charAt(++i);
                if (next_ch == 'n' || next_ch == 'N') {
                    builder.append("\n");
                } else {
                    builder.append(next_ch);
                }
            } else {
                builder.append(ch);
            }
        }
        return builder.toString();
    }

    @Override
    protected String maybeUnescapeCharacter(final char ch) {
        return unescapeCharacter(ch);
    }

    public static String unescapeCharacter(final char ch) {
        if (ch == 'n' || ch == 'N') {
            return "\n";
        } else {
            return String.valueOf(ch);
        }
    }

    @Override
    protected Set<String> getKnownPropertyNameSet() {
        return VCardParser_V30.sKnownPropertyNameSet;
    }
}
+43 −952

File changed.

Preview size limit exceeded, changes collapsed.

+28 −305

File changed.

Preview size limit exceeded, changes collapsed.

Loading