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    }