Commit | Line | Data |
---|---|---|
a3b510ab NR |
1 | package be.nikiroo.jvcard.parsers; |
2 | ||
bcb54330 | 3 | import java.util.Iterator; |
a3b510ab NR |
4 | import java.util.LinkedList; |
5 | import java.util.List; | |
6 | ||
7 | import be.nikiroo.jvcard.Card; | |
8 | import be.nikiroo.jvcard.Contact; | |
9 | import be.nikiroo.jvcard.Data; | |
10 | import be.nikiroo.jvcard.TypeInfo; | |
11 | ||
12 | public class Vcard21Parser { | |
0b6140e4 NR |
13 | /** |
14 | * Load the given data from under the given {@link Format}. | |
15 | * | |
16 | * @param lines | |
17 | * the input to load from | |
18 | * @param format | |
19 | * the {@link Format} to load as | |
20 | * | |
21 | * @return the list of elements | |
22 | */ | |
23 | public static List<Contact> parseContact(Iterable<String> textData) { | |
bcb54330 | 24 | Iterator<String> lines = textData.iterator(); |
a3b510ab | 25 | List<Contact> contacts = new LinkedList<Contact>(); |
0b6140e4 | 26 | List<String> datas = null; |
a3b510ab | 27 | |
bcb54330 NR |
28 | String nextRawLine = null; |
29 | if (lines.hasNext()) { | |
30 | nextRawLine = lines.next(); | |
31 | while (lines.hasNext() && isContinuation(nextRawLine)) { | |
32 | // BAD INPUT FILE. IGNORE. | |
33 | System.err | |
34 | .println("VCARD Parser warning: CONTINUATION line seen before any data line"); | |
35 | nextRawLine = lines.next(); | |
36 | } | |
37 | } | |
38 | ||
39 | while (nextRawLine != null) { | |
40 | StringBuilder rawLine = new StringBuilder(nextRawLine.trim()); | |
41 | if (lines.hasNext()) | |
42 | nextRawLine = lines.next(); | |
43 | else | |
44 | nextRawLine = null; | |
45 | ||
46 | while (isContinuation(nextRawLine)) { | |
47 | rawLine.append(nextRawLine.trim()); | |
48 | if (lines.hasNext()) | |
49 | nextRawLine = lines.next(); | |
50 | else | |
51 | nextRawLine = null; | |
52 | } | |
53 | ||
54 | String line = rawLine.toString(); | |
a3b510ab | 55 | if (line.equals("BEGIN:VCARD")) { |
0b6140e4 | 56 | datas = new LinkedList<String>(); |
a3b510ab NR |
57 | } else if (line.equals("END:VCARD")) { |
58 | if (datas == null) { | |
59 | // BAD INPUT FILE. IGNORE. | |
60 | System.err | |
61 | .println("VCARD Parser warning: END:VCARD seen before any VCARD:BEGIN"); | |
62 | } else { | |
0b6140e4 | 63 | contacts.add(new Contact(parseData(datas))); |
a3b510ab NR |
64 | } |
65 | } else { | |
66 | if (datas == null) { | |
67 | // BAD INPUT FILE. IGNORE. | |
68 | System.err | |
69 | .println("VCARD Parser warning: data seen before any VCARD:BEGIN"); | |
70 | } else { | |
0b6140e4 NR |
71 | datas.add(line); |
72 | } | |
73 | } | |
74 | } | |
75 | ||
76 | return contacts; | |
77 | } | |
78 | ||
79 | /** | |
80 | * Load the given data from under the given {@link Format}. | |
81 | * | |
82 | * @param lines | |
83 | * the input to load from | |
84 | * @param format | |
85 | * the {@link Format} to load as | |
86 | * | |
87 | * @return the list of elements | |
88 | */ | |
89 | public static List<Data> parseData(Iterable<String> textData) { | |
90 | List<Data> datas = new LinkedList<Data>(); | |
91 | ||
92 | for (String line : textData) { | |
93 | List<TypeInfo> types = new LinkedList<TypeInfo>(); | |
94 | String name = ""; | |
95 | String value = ""; | |
96 | String group = ""; | |
97 | ||
98 | if (line.contains(":")) { | |
99 | int colIndex = line.indexOf(':'); | |
100 | String rest = line.substring(0, colIndex); | |
101 | value = line.substring(colIndex + 1); | |
102 | ||
103 | if (rest.contains(";")) { | |
104 | String tab[] = rest.split(";"); | |
105 | name = tab[0]; | |
106 | ||
107 | for (int i = 1; i < tab.length; i++) { | |
108 | if (tab[i].contains("=")) { | |
109 | int equIndex = tab[i].indexOf('='); | |
110 | String tname = tab[i].substring(0, equIndex); | |
111 | String tvalue = tab[i].substring(equIndex + 1); | |
112 | types.add(new TypeInfo(tname, tvalue)); | |
a3b510ab | 113 | } else { |
0b6140e4 | 114 | types.add(new TypeInfo(tab[i], "")); |
a3b510ab | 115 | } |
a3b510ab | 116 | } |
0b6140e4 NR |
117 | } else { |
118 | name = rest; | |
a3b510ab | 119 | } |
0b6140e4 NR |
120 | } else { |
121 | name = line; | |
122 | } | |
123 | ||
124 | if (name.contains(".")) { | |
125 | int dotIndex = name.indexOf('.'); | |
126 | group = name.substring(0, dotIndex); | |
127 | name = name.substring(dotIndex + 1); | |
a3b510ab | 128 | } |
0b6140e4 NR |
129 | |
130 | datas.add(new Data(types, name, value, group)); | |
a3b510ab NR |
131 | } |
132 | ||
0b6140e4 | 133 | return datas; |
a3b510ab NR |
134 | } |
135 | ||
cf77cb35 NR |
136 | /** |
137 | * Return a {@link String} representation of the given {@link Card}, line by | |
138 | * line. | |
139 | * | |
140 | * @param card | |
141 | * the card to convert | |
142 | * | |
0b6140e4 NR |
143 | * @return the {@link String} representation |
144 | */ | |
145 | public static List<String> toStrings(Card card) { | |
146 | List<String> lines = new LinkedList<String>(); | |
147 | ||
148 | for (Contact contact : card) { | |
149 | lines.addAll(toStrings(contact, -1)); | |
150 | } | |
151 | ||
152 | return lines; | |
153 | } | |
154 | ||
155 | /** | |
156 | * Return a {@link String} representation of the given {@link Contact}, line | |
157 | * by line. | |
158 | * | |
159 | * @param card | |
160 | * the contact to convert | |
161 | * | |
cf77cb35 NR |
162 | * @param startingBKey |
163 | * the starting BKey number (all the other will follow) or -1 for | |
164 | * no BKey | |
165 | * | |
166 | * @return the {@link String} representation | |
167 | */ | |
168 | public static List<String> toStrings(Contact contact, int startingBKey) { | |
169 | List<String> lines = new LinkedList<String>(); | |
170 | ||
171 | lines.add("BEGIN:VCARD"); | |
172 | lines.add("VERSION:2.1"); | |
1c03abaf | 173 | for (Data data : contact) { |
0b6140e4 | 174 | lines.addAll(toStrings(data)); |
a3b510ab | 175 | } |
cf77cb35 | 176 | lines.add("END:VCARD"); |
a3b510ab | 177 | |
cf77cb35 | 178 | return lines; |
a3b510ab NR |
179 | } |
180 | ||
cf77cb35 | 181 | /** |
0b6140e4 | 182 | * Return a {@link String} representation of the given {@link Data}, line by |
cf77cb35 NR |
183 | * line. |
184 | * | |
0b6140e4 NR |
185 | * @param data |
186 | * the data to convert | |
cf77cb35 NR |
187 | * |
188 | * @return the {@link String} representation | |
189 | */ | |
0b6140e4 | 190 | public static List<String> toStrings(Data data) { |
cf77cb35 | 191 | List<String> lines = new LinkedList<String>(); |
a3b510ab | 192 | |
0b6140e4 NR |
193 | StringBuilder dataBuilder = new StringBuilder(); |
194 | if (data.getGroup() != null && !data.getGroup().trim().equals("")) { | |
195 | dataBuilder.append(data.getGroup().trim()); | |
196 | dataBuilder.append('.'); | |
197 | } | |
198 | dataBuilder.append(data.getName()); | |
199 | for (TypeInfo type : data) { | |
200 | dataBuilder.append(';'); | |
201 | dataBuilder.append(type.getName()); | |
202 | if (type.getValue() != null && !type.getValue().trim().equals("")) { | |
203 | dataBuilder.append('='); | |
204 | dataBuilder.append(type.getValue()); | |
205 | } | |
206 | } | |
207 | dataBuilder.append(':'); | |
208 | ||
209 | // TODO: bkey! | |
210 | dataBuilder.append(data.getValue()); | |
211 | ||
212 | // RFC says: Content lines SHOULD be folded to a maximum width of 75 | |
213 | // octets -> since it is SHOULD, we will just cut it as 74/75 chars | |
214 | // depending if the last one fits in one char (note: chars != octet) | |
215 | boolean continuation = false; | |
216 | while (dataBuilder.length() > 0) { | |
217 | int stop = 74; | |
218 | if (continuation) | |
219 | stop--; // the space takes 1 | |
220 | if (dataBuilder.length() > stop) { | |
221 | char car = dataBuilder.charAt(stop - 1); | |
222 | // RFC forbids cutting a character in 2 | |
223 | if (Character.isHighSurrogate(car)) { | |
224 | stop++; | |
225 | } | |
226 | } | |
227 | ||
228 | stop = Math.min(stop, dataBuilder.length()); | |
229 | if (continuation) { | |
230 | lines.add(' ' + dataBuilder.substring(0, stop)); | |
231 | } else { | |
232 | lines.add(dataBuilder.substring(0, stop)); | |
233 | } | |
234 | dataBuilder.delete(0, stop); | |
235 | ||
236 | continuation = true; | |
a3b510ab | 237 | } |
78e4af97 | 238 | |
cf77cb35 | 239 | return lines; |
a3b510ab | 240 | } |
bcb54330 NR |
241 | |
242 | /** | |
243 | * Check if the given line is a continuation line or not. | |
244 | * | |
245 | * @param line | |
246 | * the line to check | |
247 | * | |
248 | * @return TRUE if the line is a continuation line | |
249 | */ | |
250 | private static boolean isContinuation(String line) { | |
251 | if (line != null && line.length() > 0) | |
252 | return (line.charAt(0) == ' ' || line.charAt(0) == '\t'); | |
253 | return false; | |
254 | } | |
a3b510ab | 255 | } |