| /* |
| * 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; |
| |
| import android.content.ContentValues; |
| import android.pim.vcard.VCardInterpreter; |
| import android.pim.vcard.VCardConfig; |
| import android.util.CharsetUtils; |
| import android.util.Log; |
| |
| import org.apache.commons.codec.DecoderException; |
| import org.apache.commons.codec.binary.Base64; |
| import org.apache.commons.codec.net.QuotedPrintableCodec; |
| |
| import java.io.UnsupportedEncodingException; |
| import java.nio.ByteBuffer; |
| import java.nio.charset.Charset; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Store the parse result to custom datastruct: VNode, PropertyNode |
| * Maybe several vcard instance, so use vNodeList to store. |
| * VNode: standy by a vcard instance. |
| * PropertyNode: standy by a property line of a card. |
| * |
| * Previously used in main vCard handling code but now exists only for testing. |
| */ |
| public class VNodeBuilder implements VCardInterpreter { |
| static private String LOG_TAG = "VNodeBuilder"; |
| |
| /** |
| * If there's no other information available, this class uses this charset for encoding |
| * byte arrays. |
| */ |
| static public String TARGET_CHARSET = "UTF-8"; |
| |
| /** type=VNode */ |
| 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_CHARSET, TARGET_CHARSET, false); |
| } |
| |
| public VNodeBuilder(String charset, boolean strictLineBreakParsing) { |
| this(null, charset, strictLineBreakParsing); |
| } |
| |
| /** |
| * @hide sourceCharset is temporal. |
| */ |
| public VNodeBuilder(String sourceCharset, String targetCharset, |
| boolean strictLineBreakParsing) { |
| if (sourceCharset != null) { |
| mSourceCharset = sourceCharset; |
| } else { |
| mSourceCharset = VCardConfig.DEFAULT_CHARSET; |
| } |
| if (targetCharset != null) { |
| mTargetCharset = targetCharset; |
| } else { |
| mTargetCharset = TARGET_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; |
| } |
| |
| // Used only in VCard. |
| public void propertyGroup(String group) { |
| mCurrentPropNode.propGroupSet.add(group); |
| } |
| |
| public void propertyParamType(String type) { |
| mCurrentParamType = type; |
| } |
| |
| public void propertyParamValue(String value) { |
| 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.decodeBase64(value.getBytes()); |
| return value; |
| } else if (encoding.equals("QUOTED-PRINTABLE")) { |
| String quotedPrintable = value |
| .replaceAll("= ", " ").replaceAll("=\t", "\t"); |
| String[] lines; |
| if (mStrictLineBreakParsing) { |
| lines = quotedPrintable.split("\r\n"); |
| } else { |
| StringBuilder builder = new StringBuilder(); |
| int length = quotedPrintable.length(); |
| ArrayList<String> list = new ArrayList<String>(); |
| for (int i = 0; i < length; i++) { |
| char ch = quotedPrintable.charAt(i); |
| if (ch == '\n') { |
| list.add(builder.toString()); |
| builder = new StringBuilder(); |
| } else if (ch == '\r') { |
| list.add(builder.toString()); |
| builder = new StringBuilder(); |
| if (i < length - 1) { |
| char nextCh = quotedPrintable.charAt(i + 1); |
| if (nextCh == '\n') { |
| i++; |
| } |
| } |
| } else { |
| builder.append(ch); |
| } |
| } |
| String finalLine = builder.toString(); |
| if (finalLine.length() > 0) { |
| list.add(finalLine); |
| } |
| lines = list.toArray(new String[0]); |
| } |
| StringBuilder builder = new StringBuilder(); |
| for (String line : lines) { |
| if (line.endsWith("=")) { |
| line = line.substring(0, line.length() - 1); |
| } |
| builder.append(line); |
| } |
| byte[] bytes; |
| try { |
| bytes = builder.toString().getBytes(mSourceCharset); |
| } catch (UnsupportedEncodingException e1) { |
| Log.e(LOG_TAG, "Failed to encode: charset=" + mSourceCharset); |
| bytes = builder.toString().getBytes(); |
| } |
| |
| try { |
| bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes); |
| } catch (DecoderException e) { |
| Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e); |
| return ""; |
| } |
| |
| try { |
| return new String(bytes, targetCharset); |
| } catch (UnsupportedEncodingException e) { |
| Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset); |
| return new String(bytes); |
| } |
| } |
| // 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(){ |
| return null; |
| } |
| } |