5a07903ae8ca1a558abe78e64ec873ae91f76d5b
[jvcard.git] / src / be / nikiroo / jvcard / parsers / Vcard21Parser.java
1 package be.nikiroo.jvcard.parsers;
2
3 import java.io.File;
4 import java.io.FileWriter;
5 import java.io.IOException;
6 import java.util.Iterator;
7 import java.util.LinkedList;
8 import java.util.List;
9
10 import be.nikiroo.jvcard.Card;
11 import be.nikiroo.jvcard.Contact;
12 import be.nikiroo.jvcard.Data;
13 import be.nikiroo.jvcard.TypeInfo;
14
15 public class Vcard21Parser {
16 /**
17 * Load the given data from under the given {@link Format}.
18 *
19 * @param lines
20 * the input to load from
21 * @param format
22 * the {@link Format} to load as
23 *
24 * @return the list of elements
25 */
26 public static List<Contact> parseContact(Iterable<String> textData) {
27 Iterator<String> lines = textData.iterator();
28 List<Contact> contacts = new LinkedList<Contact>();
29 List<String> datas = null;
30
31 String nextRawLine = null;
32 if (lines.hasNext()) {
33 nextRawLine = lines.next();
34 while (lines.hasNext() && isContinuation(nextRawLine)) {
35 // BAD INPUT FILE. IGNORE.
36 System.err
37 .println("VCARD Parser warning: CONTINUATION line seen before any data line");
38 nextRawLine = lines.next();
39 }
40 }
41
42 while (nextRawLine != null) {
43 StringBuilder rawLine = new StringBuilder(nextRawLine.trim());
44 if (lines.hasNext())
45 nextRawLine = lines.next();
46 else
47 nextRawLine = null;
48
49 while (isContinuation(nextRawLine)) {
50 rawLine.append(nextRawLine.trim());
51 if (lines.hasNext())
52 nextRawLine = lines.next();
53 else
54 nextRawLine = null;
55 }
56
57 String line = rawLine.toString();
58 if (line.equals("BEGIN:VCARD")) {
59 datas = new LinkedList<String>();
60 } else if (line.equals("END:VCARD")) {
61 if (datas == null) {
62 // BAD INPUT FILE. IGNORE.
63 System.err
64 .println("VCARD Parser warning: END:VCARD seen before any VCARD:BEGIN");
65 } else {
66 contacts.add(new Contact(parseData(datas)));
67 }
68 } else {
69 if (datas == null) {
70 // BAD INPUT FILE. IGNORE.
71 System.err
72 .println("VCARD Parser warning: data seen before any VCARD:BEGIN");
73 } else {
74 datas.add(line);
75 }
76 }
77 }
78
79 return contacts;
80 }
81
82 /**
83 * Load the given data from under the given {@link Format}.
84 *
85 * @param lines
86 * the input to load from
87 * @param format
88 * the {@link Format} to load as
89 *
90 * @return the list of elements
91 */
92 public static List<Data> parseData(Iterable<String> textData) {
93 List<Data> datas = new LinkedList<Data>();
94
95 for (String line : textData) {
96 List<TypeInfo> types = new LinkedList<TypeInfo>();
97 String name = "";
98 String value = "";
99 String group = "";
100
101 if (line.contains(":")) {
102 int colIndex = line.indexOf(':');
103 String rest = line.substring(0, colIndex);
104 value = line.substring(colIndex + 1);
105
106 if (rest.contains(";")) {
107 String tab[] = rest.split(";");
108 name = tab[0];
109
110 for (int i = 1; i < tab.length; i++) {
111 if (tab[i].contains("=")) {
112 int equIndex = tab[i].indexOf('=');
113 String tname = tab[i].substring(0, equIndex);
114 String tvalue = tab[i].substring(equIndex + 1);
115 types.add(new TypeInfo(tname, tvalue));
116 } else {
117 types.add(new TypeInfo(tab[i], ""));
118 }
119 }
120 } else {
121 name = rest;
122 }
123 } else {
124 name = line;
125 }
126
127 if (name.contains(".")) {
128 int dotIndex = name.indexOf('.');
129 group = name.substring(0, dotIndex);
130 name = name.substring(dotIndex + 1);
131 }
132
133 datas.add(new Data(types, name, value, group));
134 }
135
136 return datas;
137 }
138
139 /**
140 * Write the given {@link Card} in the {@link Appendable}.
141 *
142 * @param writer
143 * the {@link Appendable}
144 * @param card
145 * the {@link Card} to write
146 *
147 * @throws IOException
148 * in case of IO error
149 */
150 public static void write(Appendable writer, Card card) throws IOException {
151 for (Contact contact : card) {
152 write(writer, contact, -1);
153 }
154 }
155
156 /**
157 * Write the given {@link Contact} in the {@link Appendable}.
158 *
159 * @param writer
160 * the {@link Appendable}
161 * @param contact
162 * the {@link Contact} to write
163 * @param startingBKey
164 * the starting BKey number (all the other will follow) or -1 for
165 * no BKey
166 *
167 * @throws IOException
168 * in case of IO error
169 */
170 public static void write(Appendable writer, Contact contact,
171 int startingBKey) throws IOException {
172
173 writer.append("BEGIN:VCARD\r\n");
174 writer.append("VERSION:2.1\r\n");
175 for (Data data : contact) {
176 write(writer, data);
177 }
178 writer.append("END:VCARD\r\n");
179 }
180
181 /**
182 * Write the given {@link Data} in the {@link Appendable}.
183 *
184 * @param writer
185 * the {@link Appendable}
186 * @param data
187 * the {@link Data} to write
188 *
189 * @throws IOException
190 * in case of IO error
191 */
192 public static void write(Appendable writer, Data data) throws IOException {
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.getRawValue());
205 }
206 }
207 dataBuilder.append(':');
208
209 // TODO: bkey!
210 dataBuilder.append(data.getRawValue());
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 int previous = 0;
216 for (int index = 0; index < dataBuilder.length(); previous = index) {
217 index += 74;
218 if (previous > 0)
219 index--; // the space takes 1
220 if (dataBuilder.length() > index) {
221 char car = dataBuilder.charAt(index - 1);
222 // RFC forbids cutting a character in 2
223 if (Character.isHighSurrogate(car)) {
224 index++;
225 }
226 }
227
228 index = Math.min(index, dataBuilder.length());
229 if (previous > 0)
230 writer.append(' ');
231 writer.append(dataBuilder, previous, index);
232 writer.append("\r\n");
233 }
234 }
235
236 /**
237 * Clone the given {@link Card} by exporting then importing it again in VCF.
238 *
239 * @param c
240 * the {@link Card} to clone
241 *
242 * @return the clone {@link Contact}
243 */
244 public static Card clone(Card c) {
245 try {
246 File tmp = File.createTempFile("clone", ".vcf");
247 c.saveAs(tmp, Format.VCard21);
248
249 Card clone = new Card(tmp, Format.VCard21);
250 clone.unlink();
251 tmp.delete();
252
253 return clone;
254 } catch (IOException e) {
255 e.printStackTrace();
256 }
257
258 return null;
259 }
260
261 /**
262 * Clone the given {@link Contact} by exporting then importing it again in
263 * VCF.
264 *
265 * @param c
266 * the {@link Contact} to clone
267 *
268 * @return the clone {@link Contact}
269 */
270 public static Contact clone(Contact c) {
271 try {
272 File tmp = File.createTempFile("clone", ".vcf");
273 FileWriter writer = new FileWriter(tmp);
274 write(writer, c, -1);
275 writer.close();
276
277 Card clone = new Card(tmp, Format.VCard21);
278 clone.unlink();
279 tmp.delete();
280
281 return clone.remove(0);
282 } catch (IOException e) {
283 e.printStackTrace();
284 }
285
286 return null;
287 }
288
289 /**
290 * Check if the given line is a continuation line or not.
291 *
292 * @param line
293 * the line to check
294 *
295 * @return TRUE if the line is a continuation line
296 */
297 private static boolean isContinuation(String line) {
298 if (line != null && line.length() > 0)
299 return (line.charAt(0) == ' ' || line.charAt(0) == '\t');
300 return false;
301 }
302 }