1 package be
.nikiroo
.jvcard
.parsers
;
3 import java
.util
.Iterator
;
4 import java
.util
.LinkedList
;
7 import be
.nikiroo
.jvcard
.Card
;
8 import be
.nikiroo
.jvcard
.Contact
;
9 import be
.nikiroo
.jvcard
.Data
;
10 import be
.nikiroo
.jvcard
.TypeInfo
;
12 public class Vcard21Parser
{
14 * Load the given data from under the given {@link Format}.
17 * the input to load from
19 * the {@link Format} to load as
21 * @return the list of elements
23 public static List
<Contact
> parseContact(Iterable
<String
> textData
) {
24 Iterator
<String
> lines
= textData
.iterator();
25 List
<Contact
> contacts
= new LinkedList
<Contact
>();
26 List
<String
> datas
= null;
28 String nextRawLine
= null;
29 if (lines
.hasNext()) {
30 nextRawLine
= lines
.next();
31 while (lines
.hasNext() && isContinuation(nextRawLine
)) {
32 // BAD INPUT FILE. IGNORE.
34 .println("VCARD Parser warning: CONTINUATION line seen before any data line");
35 nextRawLine
= lines
.next();
39 while (nextRawLine
!= null) {
40 StringBuilder rawLine
= new StringBuilder(nextRawLine
.trim());
42 nextRawLine
= lines
.next();
46 while (isContinuation(nextRawLine
)) {
47 rawLine
.append(nextRawLine
.trim());
49 nextRawLine
= lines
.next();
54 String line
= rawLine
.toString();
55 if (line
.equals("BEGIN:VCARD")) {
56 datas
= new LinkedList
<String
>();
57 } else if (line
.equals("END:VCARD")) {
59 // BAD INPUT FILE. IGNORE.
61 .println("VCARD Parser warning: END:VCARD seen before any VCARD:BEGIN");
63 contacts
.add(new Contact(parseData(datas
)));
67 // BAD INPUT FILE. IGNORE.
69 .println("VCARD Parser warning: data seen before any VCARD:BEGIN");
80 * Load the given data from under the given {@link Format}.
83 * the input to load from
85 * the {@link Format} to load as
87 * @return the list of elements
89 public static List
<Data
> parseData(Iterable
<String
> textData
) {
90 List
<Data
> datas
= new LinkedList
<Data
>();
92 for (String line
: textData
) {
93 List
<TypeInfo
> types
= new LinkedList
<TypeInfo
>();
98 if (line
.contains(":")) {
99 int colIndex
= line
.indexOf(':');
100 String rest
= line
.substring(0, colIndex
);
101 value
= line
.substring(colIndex
+ 1);
103 if (rest
.contains(";")) {
104 String tab
[] = rest
.split(";");
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
));
114 types
.add(new TypeInfo(tab
[i
], ""));
124 if (name
.contains(".")) {
125 int dotIndex
= name
.indexOf('.');
126 group
= name
.substring(0, dotIndex
);
127 name
= name
.substring(dotIndex
+ 1);
130 datas
.add(new Data(types
, name
, value
, group
));
137 * Return a {@link String} representation of the given {@link Card}, line by
141 * the card to convert
143 * @return the {@link String} representation
145 public static List
<String
> toStrings(Card card
) {
146 List
<String
> lines
= new LinkedList
<String
>();
148 for (Contact contact
: card
) {
149 lines
.addAll(toStrings(contact
, -1));
156 * Return a {@link String} representation of the given {@link Contact}, line
160 * the contact to convert
162 * @param startingBKey
163 * the starting BKey number (all the other will follow) or -1 for
166 * @return the {@link String} representation
168 public static List
<String
> toStrings(Contact contact
, int startingBKey
) {
169 List
<String
> lines
= new LinkedList
<String
>();
171 lines
.add("BEGIN:VCARD");
172 lines
.add("VERSION:2.1");
173 for (Data data
: contact
) {
174 lines
.addAll(toStrings(data
));
176 lines
.add("END:VCARD");
182 * Return a {@link String} representation of the given {@link Data}, line by
186 * the data to convert
188 * @return the {@link String} representation
190 public static List
<String
> toStrings(Data data
) {
191 List
<String
> lines
= new LinkedList
<String
>();
193 StringBuilder dataBuilder
= new StringBuilder();
194 if (data
.getGroup() != null && !data
.getGroup().trim().equals("")) {
195 dataBuilder
.append(data
.getGroup().trim());
196 dataBuilder
.append('.');
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());
207 dataBuilder
.append(':');
210 dataBuilder
.append(data
.getValue());
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) {
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
)) {
228 stop
= Math
.min(stop
, dataBuilder
.length());
230 lines
.add(' ' + dataBuilder
.substring(0, stop
));
232 lines
.add(dataBuilder
.substring(0, stop
));
234 dataBuilder
.delete(0, stop
);
243 * Clone the given {@link Card} by exporting then importing it again in VCF.
246 * the {@link Card} to clone
248 * @return the clone {@link Contact}
250 public static Card
clone(Card c
) {
251 return new Card(parseContact(toStrings(c
)));
255 * Clone the given {@link Contact} by exporting then importing it again in
259 * the {@link Contact} to clone
261 * @return the clone {@link Contact}
263 public static Contact
clone(Contact c
) {
264 return parseContact(toStrings(c
, -1)).get(0);
268 * Check if the given line is a continuation line or not.
273 * @return TRUE if the line is a continuation line
275 private static boolean isContinuation(String line
) {
276 if (line
!= null && line
.length() > 0)
277 return (line
.charAt(0) == ' ' || line
.charAt(0) == '\t');