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