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 }