Indexed colours, better image handling, lines cut at 74:
[jvcard.git] / src / be / nikiroo / jvcard / parsers / Vcard21Parser.java
CommitLineData
a3b510ab
NR
1package be.nikiroo.jvcard.parsers;
2
bcb54330 3import java.util.Iterator;
a3b510ab
NR
4import java.util.LinkedList;
5import java.util.List;
6
7import be.nikiroo.jvcard.Card;
8import be.nikiroo.jvcard.Contact;
9import be.nikiroo.jvcard.Data;
10import be.nikiroo.jvcard.TypeInfo;
11
12public class Vcard21Parser {
bcb54330
NR
13 public static List<Contact> parse(Iterable<String> textData) {
14 Iterator<String> lines = textData.iterator();
a3b510ab
NR
15 List<Contact> contacts = new LinkedList<Contact>();
16 List<Data> datas = null;
17
bcb54330
NR
18 String nextRawLine = null;
19 if (lines.hasNext()) {
20 nextRawLine = lines.next();
21 while (lines.hasNext() && isContinuation(nextRawLine)) {
22 // BAD INPUT FILE. IGNORE.
23 System.err
24 .println("VCARD Parser warning: CONTINUATION line seen before any data line");
25 nextRawLine = lines.next();
26 }
27 }
28
29 while (nextRawLine != null) {
30 StringBuilder rawLine = new StringBuilder(nextRawLine.trim());
31 if (lines.hasNext())
32 nextRawLine = lines.next();
33 else
34 nextRawLine = null;
35
36 while (isContinuation(nextRawLine)) {
37 rawLine.append(nextRawLine.trim());
38 if (lines.hasNext())
39 nextRawLine = lines.next();
40 else
41 nextRawLine = null;
42 }
43
44 String line = rawLine.toString();
a3b510ab
NR
45 if (line.equals("BEGIN:VCARD")) {
46 datas = new LinkedList<Data>();
47 } else if (line.equals("END:VCARD")) {
48 if (datas == null) {
49 // BAD INPUT FILE. IGNORE.
50 System.err
51 .println("VCARD Parser warning: END:VCARD seen before any VCARD:BEGIN");
52 } else {
53 contacts.add(new Contact(datas));
54 }
55 } else {
56 if (datas == null) {
57 // BAD INPUT FILE. IGNORE.
58 System.err
59 .println("VCARD Parser warning: data seen before any VCARD:BEGIN");
60 } else {
61 List<TypeInfo> types = new LinkedList<TypeInfo>();
62 String name = "";
63 String value = "";
64 String group = "";
65
66 if (line.contains(":")) {
adf9c449
NR
67 int colIndex = line.indexOf(':');
68 String rest = line.substring(0, colIndex);
69 value = line.substring(colIndex + 1);
a3b510ab
NR
70
71 if (rest.contains(";")) {
72 String tab[] = rest.split(";");
73 name = tab[0];
74
75 for (int i = 1; i < tab.length; i++) {
76 if (tab[i].contains("=")) {
adf9c449
NR
77 int equIndex = tab[i].indexOf('=');
78 String tname = tab[i]
79 .substring(0, equIndex);
80 String tvalue = tab[i]
81 .substring(equIndex + 1);
a3b510ab
NR
82 types.add(new TypeInfo(tname, tvalue));
83 } else {
84 types.add(new TypeInfo(tab[i], ""));
85 }
86 }
87 } else {
88 name = rest;
89 }
90 } else {
91 name = line;
92 }
93
94 if (name.contains(".")) {
adf9c449
NR
95 int dotIndex = name.indexOf('.');
96 group = name.substring(0, dotIndex);
97 name = name.substring(dotIndex + 1);
a3b510ab
NR
98 }
99
100 datas.add(new Data(types, name, value, group));
101 }
102 }
103 }
104
105 return contacts;
106 }
107
108 // -1 = no bkeys
109 public static String toString(Contact contact, int startingBKey) {
110 StringBuilder builder = new StringBuilder();
111
112 builder.append("BEGIN:VCARD");
113 builder.append("\r\n");
114 builder.append("VERSION:2.1");
115 builder.append("\r\n");
1c03abaf
NR
116 for (Data data : contact) {
117 StringBuilder dataBuilder = new StringBuilder();
a3b510ab 118 if (data.getGroup() != null && !data.getGroup().trim().equals("")) {
1c03abaf
NR
119 dataBuilder.append(data.getGroup().trim());
120 dataBuilder.append('.');
a3b510ab 121 }
1c03abaf
NR
122 dataBuilder.append(data.getName());
123 for (TypeInfo type : data) {
124 dataBuilder.append(';');
125 dataBuilder.append(type.getName());
a3b510ab
NR
126 if (type.getValue() != null
127 && !type.getValue().trim().equals("")) {
1c03abaf
NR
128 dataBuilder.append('=');
129 dataBuilder.append(type.getValue());
a3b510ab
NR
130 }
131 }
1c03abaf 132 dataBuilder.append(':');
bcb54330
NR
133
134 // TODO: bkey!
1c03abaf
NR
135 dataBuilder.append(data.getValue());
136
137 // RFC says: Content lines SHOULD be folded to a maximum width of 75
138 // octets -> since it is SHOULD, we will just cut it as 74/75 chars
139 // depending if the last one fits in one char (note: chars != octet)
140 boolean continuation = false;
141 while (dataBuilder.length() > 0) {
142 int stop = 74;
143 if (continuation)
144 stop--; // the space takes 1
145 if (dataBuilder.length() > stop) {
146 char car = dataBuilder.charAt(stop - 1);
147 // RFC forbids cutting a character in 2
148 if (Character.isHighSurrogate(car)) {
149 stop++;
150 }
151 }
152
153 stop = Math.min(stop, dataBuilder.length());
154 if (continuation)
155 builder.append(' ');
156 builder.append(dataBuilder, 0, stop);
157 builder.append("\r\n");
158 dataBuilder.delete(0, stop);
159
160 continuation = true;
161 }
a3b510ab
NR
162 }
163 builder.append("END:VCARD");
164 builder.append("\r\n");
165
166 return builder.toString();
167 }
168
169 public static String toString(Card card) {
170 StringBuilder builder = new StringBuilder();
171
1c03abaf
NR
172 for (Contact contact : card) {
173 builder.append(toString(contact, -1));
a3b510ab 174 }
78e4af97 175
bcb54330 176 builder.append("\r\n");
a3b510ab
NR
177
178 return builder.toString();
179 }
bcb54330
NR
180
181 /**
182 * Check if the given line is a continuation line or not.
183 *
184 * @param line
185 * the line to check
186 *
187 * @return TRUE if the line is a continuation line
188 */
189 private static boolean isContinuation(String line) {
190 if (line != null && line.length() > 0)
191 return (line.charAt(0) == ' ' || line.charAt(0) == '\t');
192 return false;
193 }
a3b510ab 194}