VCardEntryConstructor.java revision 48dd8e86a81d2ab40eb762975c8211c225002bf0
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.vcard;
17
18import android.accounts.Account;
19import android.text.TextUtils;
20import android.util.Base64;
21import android.util.Log;
22
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.List;
26
27/**
28 * <p>
29 * The {@link VCardInterpreter} implementation which enables {@link VCardEntryHandler} objects
30 * to easily handle each vCard entry.
31 * </p>
32 * <p>
33 * This class understand details inside vCard and translates it to {@link VCardEntry}.
34 * Then the class throw it to {@link VCardEntryHandler} registered via
35 * {@link #addEntryHandler(VCardEntryHandler)}, so that all those registered objects
36 * are able to handle the {@link VCardEntry} object.
37 * </p>
38 * <p>
39 * If you want to know the detail inside vCard, it would be better to implement
40 * {@link VCardInterpreter} directly, instead of relying on this class and
41 * {@link VCardEntry} created by the object.
42 * </p>
43 */
44public class VCardEntryConstructor implements VCardInterpreter {
45    private static String LOG_TAG = VCardConstants.LOG_TAG;
46
47    /**
48     * Represents current stack of VCardEntry. Used to support nested vCard (vCard 2.1).
49     */
50    private final List<VCardEntry> mEntryStack = new ArrayList<VCardEntry>();
51    private VCardEntry mCurrentEntry;
52    private VCardEntry.Property mCurrentProperty = new VCardEntry.Property();
53    private String mParamType;
54
55    // The charset using which {@link VCardInterpreter} parses the text.
56    // Each String is first decoded into binary stream with this charset, and encoded back
57    // to "target charset", which may be explicitly specified by the vCard with "CHARSET"
58    // property or implicitly mentioned by its version (e.g. vCard 3.0 recommends UTF-8).
59    private final String mSourceCharset;
60
61    private final boolean mStrictLineBreaking;
62    private final int mVCardType;
63    private final Account mAccount;
64
65    private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
66
67    public VCardEntryConstructor() {
68        this(VCardConfig.VCARD_TYPE_V21_GENERIC, null);
69    }
70
71    public VCardEntryConstructor(final int vcardType) {
72        this(vcardType, null, null, false);
73    }
74
75    public VCardEntryConstructor(final int vcardType, final Account account) {
76        this(vcardType, account, null, false);
77    }
78
79    public VCardEntryConstructor(final int vcardType, final Account account,
80            final String inputCharset) {
81        this(vcardType, account, inputCharset, false);
82    }
83
84    /**
85     * @hide Just for testing.
86     */
87    public VCardEntryConstructor(final int vcardType, final Account account,
88            final String inputCharset, final boolean strictLineBreakParsing) {
89        if (inputCharset != null) {
90            mSourceCharset = inputCharset;
91        } else {
92            mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
93        }
94        mStrictLineBreaking = strictLineBreakParsing;
95        mVCardType = vcardType;
96        mAccount = account;
97    }
98
99    public void addEntryHandler(VCardEntryHandler entryHandler) {
100        mEntryHandlers.add(entryHandler);
101    }
102
103    @Override
104    public void start() {
105        for (VCardEntryHandler entryHandler : mEntryHandlers) {
106            entryHandler.onStart();
107        }
108    }
109
110    @Override
111    public void end() {
112        for (VCardEntryHandler entryHandler : mEntryHandlers) {
113            entryHandler.onEnd();
114        }
115    }
116
117    public void clear() {
118        mCurrentEntry = null;
119        mEntryStack.clear();
120        mCurrentProperty = new VCardEntry.Property();
121    }
122
123    @Override
124    public void startEntry() {
125        mCurrentEntry = new VCardEntry(mVCardType, mAccount);
126        mEntryStack.add(mCurrentEntry);
127    }
128
129    @Override
130    public void endEntry() {
131        mCurrentEntry.consolidateFields();
132        for (VCardEntryHandler entryHandler : mEntryHandlers) {
133            entryHandler.onEntryCreated(mCurrentEntry);
134        }
135
136        final int size = mEntryStack.size();
137        if (size > 1) {
138            VCardEntry parent = mEntryStack.get(size - 2);
139            parent.addChild(mCurrentEntry);
140            mCurrentEntry = parent;
141        } else {
142            mCurrentEntry = null;
143        }
144        mEntryStack.remove(size - 1);
145    }
146
147    @Override
148    public void startProperty() {
149        mCurrentProperty.clear();
150    }
151
152    @Override
153    public void endProperty() {
154        mCurrentEntry.addProperty(mCurrentProperty);
155    }
156
157    @Override
158    public void propertyName(String name) {
159        mCurrentProperty.setPropertyName(name);
160    }
161
162    @Override
163    public void propertyGroup(String group) {
164    }
165
166    @Override
167    public void propertyParamType(String type) {
168        if (mParamType != null) {
169            Log.e(LOG_TAG, "propertyParamType() is called more than once " +
170                    "before propertyParamValue() is called");
171        }
172        mParamType = type;
173    }
174
175    @Override
176    public void propertyParamValue(String value) {
177        if (mParamType == null) {
178            // From vCard 2.1 specification. vCard 3.0 formally does not allow this case.
179            mParamType = "TYPE";
180        }
181        if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) {
182            value = VCardUtils.convertStringCharset(
183                    value, mSourceCharset, VCardConfig.DEFAULT_IMPORT_CHARSET);
184        }
185        mCurrentProperty.addParameter(mParamType, value);
186        mParamType = null;
187    }
188
189    private String handleOneValue(String value,
190            String sourceCharset, String targetCharset, String encoding) {
191        // It is possible when some of multiple values are empty.
192        // e.g. N:;a;;; -> values are "", "a", "", "", and "".
193        if (TextUtils.isEmpty(value)) {
194            return "";
195        }
196
197        if (encoding != null) {
198            if (encoding.equals("BASE64") || encoding.equals("B")) {
199                mCurrentProperty.setPropertyBytes(Base64.decode(value.getBytes(), Base64.DEFAULT));
200                return value;
201            } else if (encoding.equals("QUOTED-PRINTABLE")) {
202                return VCardUtils.parseQuotedPrintable(
203                        value, mStrictLineBreaking, sourceCharset, targetCharset);
204            }
205            Log.w(LOG_TAG, "Unknown encoding. Fall back to default.");
206        }
207
208        // Just translate the charset of a given String from inputCharset to a system one.
209        return VCardUtils.convertStringCharset(value, sourceCharset, targetCharset);
210    }
211
212    @Override
213    public void propertyValues(List<String> values) {
214        if (values == null || values.isEmpty()) {
215            return;
216        }
217
218        final Collection<String> charsetCollection =
219                mCurrentProperty.getParameters(VCardConstants.PARAM_CHARSET);
220        final Collection<String> encodingCollection =
221                mCurrentProperty.getParameters(VCardConstants.PARAM_ENCODING);
222        final String encoding =
223            ((encodingCollection != null) ? encodingCollection.iterator().next() : null);
224
225        // String targetCharset = CharsetUtils.nameForDefaultVendor(
226        //    ((charsetCollection != null) ? charsetCollection.iterator().next() : null));
227        String targetCharset =
228            ((charsetCollection != null) ? charsetCollection.iterator().next() : null);
229        if (TextUtils.isEmpty(targetCharset)) {
230            targetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
231        }
232
233        for (final String value : values) {
234            mCurrentProperty.addPropertyValue(
235                    handleOneValue(value, mSourceCharset, targetCharset, encoding));
236        }
237    }
238}
239