blob: 883703407352dd6804f767e21858166ecfec088f [file] [log] [blame]
/*
* Copyright (C) 2009 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.test_utils;
import android.content.ContentValues;
import android.pim.vcard.VCardConfig;
import android.pim.vcard.VCardInterpreter;
import android.pim.vcard.VCardUtils;
import android.util.Base64;
import android.util.CharsetUtils;
import android.util.Log;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* The class storing the parse result to custom datastruct:
* {@link VNode}, and {@link PropertyNode}.
* Maybe several vcard instance, so use vNodeList to store.
* </p>
* <p>
* This is called VNode, not VCardNode, since it was used for expressing vCalendar (iCal).
* </p>
*/
public class VNodeBuilder implements VCardInterpreter {
static private String LOG_TAG = "VNodeBuilder";
public List<VNode> vNodeList = new ArrayList<VNode>();
private int mNodeListPos = 0;
private VNode mCurrentVNode;
private PropertyNode mCurrentPropNode;
private String mCurrentParamType;
/**
* The charset using which VParser parses the text.
*/
private String mSourceCharset;
/**
* The charset with which byte array is encoded to String.
*/
private String mTargetCharset;
private boolean mStrictLineBreakParsing;
public VNodeBuilder() {
this(VCardConfig.DEFAULT_IMPORT_CHARSET, false);
}
public VNodeBuilder(String targetCharset, boolean strictLineBreakParsing) {
mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
if (targetCharset != null) {
mTargetCharset = targetCharset;
} else {
mTargetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
}
mStrictLineBreakParsing = strictLineBreakParsing;
}
public void start() {
}
public void end() {
}
// Note: I guess that this code assumes the Record may nest like this:
// START:VPOS
// ...
// START:VPOS2
// ...
// END:VPOS2
// ...
// END:VPOS
//
// However the following code has a bug.
// When error occurs after calling startRecord(), the entry which is probably
// the cause of the error remains to be in vNodeList, while endRecord() is not called.
//
// I leave this code as is since I'm not familiar with vcalendar specification.
// But I believe we should refactor this code in the future.
// Until this, the last entry has to be removed when some error occurs.
public void startEntry() {
VNode vnode = new VNode();
vnode.parseStatus = 1;
vnode.VName = "VCARD";
// I feel this should be done in endRecord(), but it cannot be done because of
// the reason above.
vNodeList.add(vnode);
mNodeListPos = vNodeList.size() - 1;
mCurrentVNode = vNodeList.get(mNodeListPos);
}
public void endEntry() {
VNode endNode = vNodeList.get(mNodeListPos);
endNode.parseStatus = 0;
while(mNodeListPos > 0){
mNodeListPos--;
if((vNodeList.get(mNodeListPos)).parseStatus == 1)
break;
}
mCurrentVNode = vNodeList.get(mNodeListPos);
}
public void startProperty() {
mCurrentPropNode = new PropertyNode();
}
public void endProperty() {
mCurrentVNode.propList.add(mCurrentPropNode);
}
public void propertyName(String name) {
mCurrentPropNode.propName = name;
}
public void propertyGroup(String group) {
mCurrentPropNode.propGroupSet.add(group);
}
public void propertyParamType(String type) {
mCurrentParamType = type;
}
public void propertyParamValue(String value) {
if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) {
value = VCardUtils.convertStringCharset(value,
VCardConfig.DEFAULT_INTERMEDIATE_CHARSET,
VCardConfig.DEFAULT_IMPORT_CHARSET);
}
if (mCurrentParamType == null ||
mCurrentParamType.equalsIgnoreCase("TYPE")) {
mCurrentPropNode.paramMap_TYPE.add(value);
} else {
mCurrentPropNode.paramMap.put(mCurrentParamType, value);
}
mCurrentParamType = null;
}
private String encodeString(String originalString, String targetCharset) {
if (mSourceCharset.equalsIgnoreCase(targetCharset)) {
return originalString;
}
Charset charset = Charset.forName(mSourceCharset);
ByteBuffer byteBuffer = charset.encode(originalString);
// byteBuffer.array() "may" return byte array which is larger than
// byteBuffer.remaining(). Here, we keep on the safe side.
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
try {
return new String(bytes, targetCharset);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
return null;
}
}
private String handleOneValue(String value, String targetCharset, String encoding) {
if (encoding != null) {
encoding = encoding.toUpperCase();
if (encoding.equals("BASE64") || encoding.equals("B")) {
// Assume BASE64 is used only when the number of values is 1.
mCurrentPropNode.propValue_bytes = Base64.decode(value.getBytes(), Base64.NO_WRAP);
return value;
} else if (encoding.equals("QUOTED-PRINTABLE")) {
return VCardUtils.parseQuotedPrintable(
value, mStrictLineBreakParsing, mSourceCharset, targetCharset);
}
// Unknown encoding. Fall back to default.
}
return encodeString(value, targetCharset);
}
public void propertyValues(List<String> values) {
if (values == null || values.size() == 0) {
mCurrentPropNode.propValue_bytes = null;
mCurrentPropNode.propValue_vector.clear();
mCurrentPropNode.propValue_vector.add("");
mCurrentPropNode.propValue = "";
return;
}
ContentValues paramMap = mCurrentPropNode.paramMap;
String targetCharset = CharsetUtils.nameForDefaultVendor(paramMap.getAsString("CHARSET"));
String encoding = paramMap.getAsString("ENCODING");
if (targetCharset == null || targetCharset.length() == 0) {
targetCharset = mTargetCharset;
}
for (String value : values) {
mCurrentPropNode.propValue_vector.add(
handleOneValue(value, targetCharset, encoding));
}
mCurrentPropNode.propValue = listToString(mCurrentPropNode.propValue_vector);
}
private String listToString(List<String> list){
int size = list.size();
if (size > 1) {
StringBuilder typeListB = new StringBuilder();
for (String type : list) {
typeListB.append(type).append(";");
}
int len = typeListB.length();
if (len > 0 && typeListB.charAt(len - 1) == ';') {
return typeListB.substring(0, len - 1);
}
return typeListB.toString();
} else if (size == 1) {
return list.get(0);
} else {
return "";
}
}
public String getResult(){
throw new RuntimeException("Not supported");
}
}