001 package ezvcard.io; 002 003 import java.util.ArrayList; 004 import java.util.List; 005 006 import ezvcard.VCardVersion; 007 import ezvcard.util.VCardStringUtils; 008 009 /* 010 Copyright (c) 2012, Michael Angstadt 011 All rights reserved. 012 013 Redistribution and use in source and binary forms, with or without 014 modification, are permitted provided that the following conditions are met: 015 016 1. Redistributions of source code must retain the above copyright notice, this 017 list of conditions and the following disclaimer. 018 2. Redistributions in binary form must reproduce the above copyright notice, 019 this list of conditions and the following disclaimer in the documentation 020 and/or other materials provided with the distribution. 021 022 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 023 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 024 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 025 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 026 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 027 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 028 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 029 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 030 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 031 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 032 033 The views and conclusions contained in the software and documentation are those 034 of the authors and should not be interpreted as representing official policies, 035 either expressed or implied, of the FreeBSD Project. 036 */ 037 038 /** 039 * Represents the components that make up an unfolded vCard line, such as type 040 * name and value. 041 * @author Michael Angstadt 042 */ 043 public class VCardLine { 044 private String group; 045 private String typeName; 046 private List<List<String>> subTypes = new ArrayList<List<String>>(); 047 private String value; 048 049 private VCardLine() { 050 //hide constructor 051 } 052 053 /** 054 * Parses an unfolded vCard line. It just parses the components out, it 055 * doesn't modify the components in any way. 056 * @param line the unfolded line to parse 057 * @param version the version of the vCard that's being parsed 058 * @param caretDecodingEnabled true to enable circumflex accent decoding in 059 * 3.0 and 4.0 parameter values, false not to 060 * @return the parsed components or null if the line is not a valid vCard 061 * line 062 */ 063 public static VCardLine parse(String line, VCardVersion version, boolean caretDecodingEnabled) { 064 VCardLine lineObj = new VCardLine(); 065 066 char escapeChar = 0; //is the next char escaped? 067 boolean inQuotes = false; //are we inside of double quotes? 068 StringBuilder buf = new StringBuilder(); 069 List<String> curSubType = new ArrayList<String>(); 070 curSubType.add(null); 071 for (int i = 0; i < line.length(); i++) { 072 char ch = line.charAt(i); 073 if (escapeChar != 0) { 074 if (escapeChar == '\\') { 075 if (ch == '\\') { 076 buf.append(ch); 077 } else if (ch == 'n' || ch == 'N') { 078 //newlines appear as "\n" or "\N" (see RFC 2426 p.7) 079 buf.append(System.getProperty("line.separator")); 080 } else if (ch == '"' && version != VCardVersion.V2_1) { 081 //double quotes don't need to be escaped in 2.1 parameter values because they have no special meaning 082 buf.append(ch); 083 } else if (ch == ';' && version == VCardVersion.V2_1) { 084 //semi-colons can only be escaped in 2.1 parameter values (see section 2 of specs) 085 //if a 3.0/4.0 param value has semi-colons, the value should be surrounded in double quotes 086 buf.append(ch); 087 } else { 088 //treat the escape character as a normal character because it's not a valid escape sequence 089 buf.append(escapeChar).append(ch); 090 } 091 } else if (escapeChar == '^') { 092 if (ch == '^') { 093 buf.append(ch); 094 } else if (ch == 'n') { 095 buf.append(System.getProperty("line.separator")); 096 } else if (ch == '\'') { 097 buf.append('"'); 098 } else { 099 //treat the escape character as a normal character because it's not a valid escape sequence 100 buf.append(escapeChar).append(ch); 101 } 102 } 103 escapeChar = 0; 104 } else if (ch == '\\' || (ch == '^' && version != VCardVersion.V2_1 && caretDecodingEnabled)) { 105 escapeChar = ch; 106 } else if (ch == '.' && lineObj.group == null && lineObj.typeName == null) { 107 lineObj.group = buf.toString(); 108 buf.setLength(0); 109 } else if ((ch == ';' || ch == ':') && !inQuotes) { 110 if (lineObj.typeName == null) { 111 lineObj.typeName = buf.toString(); 112 } else { 113 //sub type value 114 String subTypeValue = buf.toString(); 115 if (version == VCardVersion.V2_1) { 116 //2.1 allows whitespace to surround the "=", so remove it 117 subTypeValue = VCardStringUtils.ltrim(subTypeValue); 118 } 119 curSubType.add(subTypeValue); 120 121 lineObj.subTypes.add(curSubType); 122 123 curSubType = new ArrayList<String>(); 124 curSubType.add(null); 125 } 126 buf.setLength(0); 127 128 if (ch == ':') { 129 if (i < line.length() - 1) { 130 lineObj.value = line.substring(i + 1); 131 } else { 132 lineObj.value = ""; 133 } 134 break; 135 } 136 } else if (ch == ',' && !inQuotes && version != VCardVersion.V2_1) { 137 //multi-valued sub type 138 curSubType.add(buf.toString()); 139 buf.setLength(0); 140 } else if (ch == '=' && curSubType.get(0) == null) { 141 //sub type name 142 String subTypeName = buf.toString(); 143 if (version == VCardVersion.V2_1) { 144 //2.1 allows whitespace to surround the "=", so remove it 145 subTypeName = VCardStringUtils.rtrim(subTypeName); 146 } 147 curSubType.set(0, subTypeName); 148 149 buf.setLength(0); 150 } else if (ch == '"' && version != VCardVersion.V2_1) { 151 //2.1 doesn't use the quoting mechanism 152 inQuotes = !inQuotes; 153 } else { 154 buf.append(ch); 155 } 156 } 157 158 if (lineObj.typeName == null || lineObj.value == null) { 159 return null; 160 } 161 return lineObj; 162 } 163 164 /** 165 * Gets the group. 166 * @return the group or null if the type doesn't below to a group 167 */ 168 public String getGroup() { 169 return group; 170 } 171 172 /** 173 * Gets the type name. 174 * @return the type name 175 */ 176 public String getTypeName() { 177 return typeName; 178 } 179 180 /** 181 * Gets the sub types. 182 * @return the sub types. Index 0 of each list is the sub type name. The 183 * rest of the list contains the sub type values (there will always be at 184 * least one value and there may be more if the sub type is multi-valued). 185 * If the sub type is nameless, then index 0 will be null (sub types can be 186 * nameless in v2.1, e.g. "ADR;WORK;DOM:") 187 */ 188 public List<List<String>> getSubTypes() { 189 return subTypes; 190 } 191 192 /** 193 * Gets the value. 194 * @return the value 195 */ 196 public String getValue() { 197 return value; 198 } 199 }