1/*
2 This file is part of libkabc.
3 Copyright (c) 2003 Tobias Koenig <tokoe@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21#include "vcardtool.h"
22#include "key.h"
23#include "picture.h"
24#include "secrecy.h"
25#include "sound.h"
26
27#include <QtCore/QString>
28#include <QtCore/QBuffer>
29
30using namespace KABC;
31
32static bool needsEncoding( const QString &value )
33{
34 uint length = value.length();
35 for ( uint i = 0; i < length; ++i ) {
36 char c = value.at( i ).toLatin1();
37 if ( ( c < 33 || c > 126 ) && c != ' ' && c != '=' ) {
38 return true;
39 }
40 }
41
42 return false;
43}
44
45VCardTool::VCardTool()
46{
47 mAddressTypeMap.insert( QLatin1String( "dom" ), Address::Dom );
48 mAddressTypeMap.insert( QLatin1String( "intl" ), Address::Intl );
49 mAddressTypeMap.insert( QLatin1String( "postal" ), Address::Postal );
50 mAddressTypeMap.insert( QLatin1String( "parcel" ), Address::Parcel );
51 mAddressTypeMap.insert( QLatin1String( "home" ), Address::Home );
52 mAddressTypeMap.insert( QLatin1String( "work" ), Address::Work );
53 mAddressTypeMap.insert( QLatin1String( "pref" ), Address::Pref );
54
55 mPhoneTypeMap.insert( QLatin1String( "HOME" ), PhoneNumber::Home );
56 mPhoneTypeMap.insert( QLatin1String( "WORK" ), PhoneNumber::Work );
57 mPhoneTypeMap.insert( QLatin1String( "MSG" ), PhoneNumber::Msg );
58 mPhoneTypeMap.insert( QLatin1String( "PREF" ), PhoneNumber::Pref );
59 mPhoneTypeMap.insert( QLatin1String( "VOICE" ), PhoneNumber::Voice );
60 mPhoneTypeMap.insert( QLatin1String( "FAX" ), PhoneNumber::Fax );
61 mPhoneTypeMap.insert( QLatin1String( "CELL" ), PhoneNumber::Cell );
62 mPhoneTypeMap.insert( QLatin1String( "VIDEO" ), PhoneNumber::Video );
63 mPhoneTypeMap.insert( QLatin1String( "BBS" ), PhoneNumber::Bbs );
64 mPhoneTypeMap.insert( QLatin1String( "MODEM" ), PhoneNumber::Modem );
65 mPhoneTypeMap.insert( QLatin1String( "CAR" ), PhoneNumber::Car );
66 mPhoneTypeMap.insert( QLatin1String( "ISDN" ), PhoneNumber::Isdn );
67 mPhoneTypeMap.insert( QLatin1String( "PCS" ), PhoneNumber::Pcs );
68 mPhoneTypeMap.insert( QLatin1String( "PAGER" ), PhoneNumber::Pager );
69}
70
71VCardTool::~VCardTool()
72{
73}
74
75QByteArray VCardTool::exportVCards( const Addressee::List &list, VCard::Version version ) const
76{
77 return createVCards( list, version, true /*export vcard*/);
78}
79
80QByteArray VCardTool::createVCards( const Addressee::List &list, VCard::Version version ) const
81{
82 return createVCards( list, version, false /*don't export*/);
83}
84
85QByteArray VCardTool::createVCards( const Addressee::List &list,
86 VCard::Version version, bool exportVcard ) const
87{
88 VCard::List vCardList;
89
90 Addressee::List::ConstIterator addrIt;
91 Addressee::List::ConstIterator listEnd( list.constEnd() );
92 for ( addrIt = list.constBegin(); addrIt != listEnd; ++addrIt ) {
93 VCard card;
94 QStringList::ConstIterator strIt;
95
96 // ADR + LABEL
97 const Address::List addresses = ( *addrIt ).addresses();
98 for ( Address::List::ConstIterator it = addresses.begin(); it != addresses.end(); ++it ) {
99 QStringList address;
100
101 const bool isEmpty = ( ( *it ).postOfficeBox().isEmpty() &&
102 ( *it ).extended().isEmpty() &&
103 ( *it ).street().isEmpty() &&
104 ( *it ).locality().isEmpty() &&
105 ( *it ).region().isEmpty() &&
106 ( *it ).postalCode().isEmpty() &&
107 ( *it ).country().isEmpty() );
108
109 address.append( ( *it ).postOfficeBox().replace( QLatin1Char( ';' ),
110 QLatin1String( "\\;" ) ) );
111
112 address.append( ( *it ).extended().replace( QLatin1Char( ';' ),
113 QLatin1String( "\\;" ) ) );
114
115 address.append( ( *it ).street().replace( QLatin1Char( ';' ),
116 QLatin1String( "\\;" ) ) );
117
118 address.append( ( *it ).locality().replace( QLatin1Char( ';' ),
119 QLatin1String( "\\;" ) ) );
120
121 address.append( ( *it ).region().replace( QLatin1Char( ';' ),
122 QLatin1String( "\\;" ) ) );
123
124 address.append( ( *it ).postalCode().replace( QLatin1Char( ';' ),
125 QLatin1String( "\\;" ) ) );
126
127 address.append( ( *it ).country().replace( QLatin1Char( ';' ),
128 QLatin1String( "\\;" ) ) );
129
130 VCardLine adrLine( QLatin1String( "ADR" ), address.join( QLatin1String( ";" ) ) );
131 if ( version == VCard::v2_1 && needsEncoding( address.join( QLatin1String( ";" ) ) ) ) {
132 adrLine.addParameter( QLatin1String( "charset" ), QLatin1String( "UTF-8" ) );
133 adrLine.addParameter( QLatin1String( "encoding" ), QLatin1String( "QUOTED-PRINTABLE" ) );
134 }
135
136 VCardLine labelLine( QLatin1String( "LABEL" ), ( *it ).label() );
137 if ( version == VCard::v2_1 && needsEncoding( ( *it ).label() ) ) {
138 labelLine.addParameter( QLatin1String( "charset" ), QLatin1String( "UTF-8" ) );
139 labelLine.addParameter( QLatin1String( "encoding" ), QLatin1String( "QUOTED-PRINTABLE" ) );
140 }
141
142 const bool hasLabel = !( *it ).label().isEmpty();
143 QMap<QString, Address::TypeFlag>::ConstIterator typeIt;
144 for ( typeIt = mAddressTypeMap.constBegin();
145 typeIt != mAddressTypeMap.constEnd(); ++typeIt ) {
146 if ( typeIt.value() & ( *it ).type() ) {
147 adrLine.addParameter( QLatin1String( "TYPE" ), typeIt.key() );
148 if ( hasLabel ) {
149 labelLine.addParameter( QLatin1String( "TYPE" ), typeIt.key() );
150 }
151 }
152 }
153
154 if ( !isEmpty ) {
155 card.addLine( adrLine );
156 }
157 if ( hasLabel ) {
158 card.addLine( labelLine );
159 }
160 }
161
162 // BDAY
163 card.addLine( VCardLine( QLatin1String( "BDAY" ), createDateTime( ( *addrIt ).birthday() ) ) );
164
165 // CATEGORIES only > 2.1
166 if ( version != VCard::v2_1 ) {
167 QStringList categories = ( *addrIt ).categories();
168 QStringList::Iterator catIt;
169 QStringList::Iterator catEnd( categories.end() );
170 for ( catIt = categories.begin(); catIt != catEnd; ++catIt ) {
171 ( *catIt ).replace( QLatin1Char( ',' ), QLatin1String( "\\," ) );
172 }
173
174 VCardLine catLine( QLatin1String( "CATEGORIES" ), categories.join( QLatin1String( "," ) ) );
175 card.addLine( catLine );
176 }
177
178 // CLASS only for version == 3.0
179 if ( version == VCard::v3_0 ) {
180 card.addLine( createSecrecy( ( *addrIt ).secrecy() ) );
181 }
182
183 // EMAIL
184 const QStringList emails = ( *addrIt ).emails();
185 bool pref = true;
186 for ( strIt = emails.begin(); strIt != emails.end(); ++strIt ) {
187 VCardLine line( QLatin1String( "EMAIL" ), *strIt );
188 if ( pref == true && emails.count() > 1 ) {
189 line.addParameter( QLatin1String( "TYPE" ), QLatin1String( "PREF" ) );
190 pref = false;
191 }
192 card.addLine( line );
193 }
194
195 // FN required for only version > 2.1
196 VCardLine fnLine( QLatin1String( "FN" ), ( *addrIt ).formattedName() );
197 if ( version == VCard::v2_1 && needsEncoding( ( *addrIt ).formattedName() ) ) {
198 fnLine.addParameter( QLatin1String( "charset" ), QLatin1String( "UTF-8" ) );
199 fnLine.addParameter( QLatin1String( "encoding" ), QLatin1String( "QUOTED-PRINTABLE" ) );
200 }
201 card.addLine( fnLine );
202
203 // GEO
204 const Geo geo = ( *addrIt ).geo();
205 if ( geo.isValid() ) {
206 QString str;
207 str.sprintf( "%.6f;%.6f", geo.latitude(), geo.longitude() );
208 card.addLine( VCardLine( QLatin1String( "GEO" ), str ) );
209 }
210
211 // KEY
212 const Key::List keys = ( *addrIt ).keys();
213 Key::List::ConstIterator keyIt;
214 Key::List::ConstIterator keyEnd( keys.end() );
215 for ( keyIt = keys.begin(); keyIt != keyEnd; ++keyIt ) {
216 card.addLine( createKey( *keyIt ) );
217 }
218
219 // LOGO
220 card.addLine( createPicture( QLatin1String( "LOGO" ), ( *addrIt ).logo() ) );
221
222 // MAILER only for version < 4.0
223 if ( version != VCard::v4_0 ) {
224 VCardLine mailerLine( QLatin1String( "MAILER" ), ( *addrIt ).mailer() );
225 if ( version == VCard::v2_1 && needsEncoding( ( *addrIt ).mailer() ) ) {
226 mailerLine.addParameter( QLatin1String( "charset" ), QLatin1String( "UTF-8" ) );
227 mailerLine.addParameter( QLatin1String( "encoding" ), QLatin1String( "QUOTED-PRINTABLE" ) );
228 }
229 card.addLine( mailerLine );
230 }
231
232 // N required for only version < 4.0
233 QStringList name;
234 name.append( ( *addrIt ).familyName().replace( QLatin1Char( ';' ),
235 QLatin1String( "\\;" ) ) );
236
237 name.append( ( *addrIt ).givenName().replace( QLatin1Char( ';' ),
238 QLatin1String( "\\;" ) ) );
239
240 name.append( ( *addrIt ).additionalName().replace( QLatin1Char( ';' ),
241 QLatin1String( "\\;" ) ) );
242
243 name.append( ( *addrIt ).prefix().replace( QLatin1Char( ';' ),
244 QLatin1String( "\\;" ) ) );
245
246 name.append( ( *addrIt ).suffix().replace( QLatin1Char( ';' ),
247 QLatin1String( "\\;" ) ) );
248
249 VCardLine nLine( QLatin1String( "N" ), name.join( QLatin1String( ";" ) ) );
250 if ( version == VCard::v2_1 && needsEncoding( name.join( QLatin1String( ";" ) ) ) ) {
251 nLine.addParameter( QLatin1String( "charset" ), QLatin1String( "UTF-8" ) );
252 nLine.addParameter( QLatin1String( "encoding" ), QLatin1String( "QUOTED-PRINTABLE" ) );
253 }
254 if ( version == VCard::v4_0 && !( *addrIt ).sortString().isEmpty() ) {
255 nLine.addParameter( QLatin1String( "SORT-AS" ), ( *addrIt ).sortString() );
256 }
257
258 card.addLine( nLine );
259
260 // NAME only for version < 4.0
261 if ( version != VCard::v4_0 ) {
262 VCardLine nameLine( QLatin1String( "NAME" ), ( *addrIt ).name() );
263 if ( version == VCard::v2_1 && needsEncoding( ( *addrIt ).name() ) ) {
264 nameLine.addParameter( QLatin1String( "charset" ), QLatin1String( "UTF-8" ) );
265 nameLine.addParameter( QLatin1String( "encoding" ), QLatin1String( "QUOTED-PRINTABLE" ) );
266 }
267 card.addLine( nameLine );
268 }
269
270 // NICKNAME only for version > 2.1
271 if ( version != VCard::v2_1 ) {
272 card.addLine( VCardLine( QLatin1String( "NICKNAME" ), ( *addrIt ).nickName() ) );
273 }
274
275 // NOTE
276 VCardLine noteLine( QLatin1String( "NOTE" ), ( *addrIt ).note() );
277 if ( version == VCard::v2_1 && needsEncoding( ( *addrIt ).note() ) ) {
278 noteLine.addParameter( QLatin1String( "charset" ), QLatin1String( "UTF-8" ) );
279 noteLine.addParameter( QLatin1String( "encoding" ), QLatin1String( "QUOTED-PRINTABLE" ) );
280 }
281 card.addLine( noteLine );
282
283 // ORG
284 QStringList organization;
285 organization.append( ( *addrIt ).organization().replace( QLatin1Char( ';' ),
286 QLatin1String( "\\;" ) ) );
287 if ( !( *addrIt ).department().isEmpty() ) {
288 organization.append( ( *addrIt ).department().replace( QLatin1Char( ';' ),
289 QLatin1String( "\\;" ) ) );
290 }
291 VCardLine orgLine( QLatin1String( "ORG" ), organization.join( QLatin1String( ";" ) ) );
292 if ( version == VCard::v2_1 && needsEncoding( organization.join( QLatin1String( ";" ) ) ) ) {
293 orgLine.addParameter( QLatin1String( "charset" ), QLatin1String( "UTF-8" ) );
294 orgLine.addParameter( QLatin1String( "encoding" ), QLatin1String( "QUOTED-PRINTABLE" ) );
295 }
296 card.addLine( orgLine );
297
298 // PHOTO
299 card.addLine( createPicture( QLatin1String( "PHOTO" ), ( *addrIt ).photo() ) );
300
301 // PROID only for version > 2.1
302 if ( version != VCard::v2_1 ) {
303 card.addLine( VCardLine( QLatin1String( "PRODID" ), ( *addrIt ).productId() ) );
304 }
305
306 // REV
307 card.addLine( VCardLine( QLatin1String( "REV" ), createDateTime( ( *addrIt ).revision() ) ) );
308
309 // ROLE
310 VCardLine roleLine( QLatin1String( "ROLE" ), ( *addrIt ).role() );
311 if ( version == VCard::v2_1 && needsEncoding( ( *addrIt ).role() ) ) {
312 roleLine.addParameter( QLatin1String( "charset" ), QLatin1String( "UTF-8" ) );
313 roleLine.addParameter( QLatin1String( "encoding" ), QLatin1String( "QUOTED-PRINTABLE" ) );
314 }
315 card.addLine( roleLine );
316
317 // SORT-STRING
318 if ( version == VCard::v3_0 ) {
319 card.addLine( VCardLine( QLatin1String( "SORT-STRING" ), ( *addrIt ).sortString() ) );
320 }
321
322 // SOUND
323 card.addLine( createSound( ( *addrIt ).sound() ) );
324
325 // TEL
326 const PhoneNumber::List phoneNumbers = ( *addrIt ).phoneNumbers();
327 PhoneNumber::List::ConstIterator phoneIt;
328 PhoneNumber::List::ConstIterator phoneEnd( phoneNumbers.end() );
329 for ( phoneIt = phoneNumbers.begin(); phoneIt != phoneEnd; ++phoneIt ) {
330 VCardLine line( QLatin1String( "TEL" ), ( *phoneIt ).number() );
331
332 QMap<QString, PhoneNumber::TypeFlag>::ConstIterator typeIt;
333 QMap<QString, PhoneNumber::TypeFlag>::ConstIterator typeEnd( mPhoneTypeMap.constEnd() );
334 for ( typeIt = mPhoneTypeMap.constBegin(); typeIt != typeEnd; ++typeIt ) {
335 if ( typeIt.value() & ( *phoneIt ).type() ) {
336 line.addParameter( QLatin1String( "TYPE" ), typeIt.key() );
337 }
338 }
339
340 card.addLine( line );
341 }
342
343 // TITLE
344 VCardLine titleLine( QLatin1String( "TITLE" ), ( *addrIt ).title() );
345 if ( version == VCard::v2_1 && needsEncoding( ( *addrIt ).title() ) ) {
346 titleLine.addParameter( QLatin1String( "charset" ), QLatin1String( "UTF-8" ) );
347 titleLine.addParameter( QLatin1String( "encoding" ), QLatin1String( "QUOTED-PRINTABLE" ) );
348 }
349 card.addLine( titleLine );
350
351 // TZ
352 const TimeZone timeZone = ( *addrIt ).timeZone();
353 if ( timeZone.isValid() ) {
354 QString str;
355
356 int neg = 1;
357 if ( timeZone.offset() < 0 ) {
358 neg = -1;
359 }
360
361 str.sprintf( "%c%02d:%02d", ( timeZone.offset() >= 0 ? '+' : '-' ),
362 ( timeZone.offset() / 60 ) * neg,
363 ( timeZone.offset() % 60 ) * neg );
364
365 card.addLine( VCardLine( QLatin1String( "TZ" ), str ) );
366 }
367
368 // UID
369 card.addLine( VCardLine( QLatin1String( "UID" ), ( *addrIt ).uid() ) );
370
371 // URL
372 card.addLine( VCardLine( QLatin1String( "URL" ), ( *addrIt ).url().url() ) );
373
374 // VERSION
375 if ( version == VCard::v2_1 ) {
376 card.addLine( VCardLine( QLatin1String( "VERSION" ), QLatin1String( "2.1" ) ) );
377 } else if ( version == VCard::v3_0 ) {
378 card.addLine( VCardLine( QLatin1String( "VERSION" ), QLatin1String( "3.0" ) ) );
379 } else if ( version == VCard::v4_0 ) {
380 card.addLine( VCardLine( QLatin1String( "VERSION" ), QLatin1String( "4.0" ) ) );
381 }
382
383 // X-
384 const QStringList customs = ( *addrIt ).customs();
385 for ( strIt = customs.begin(); strIt != customs.end(); ++strIt ) {
386 QString identifier = QLatin1String( "X-" ) +
387 ( *strIt ).left( ( *strIt ).indexOf( QLatin1Char( ':' ) ) );
388 const QString value = ( *strIt ).mid( ( *strIt ).indexOf( QLatin1Char( ':' ) ) + 1 );
389 if ( value.isEmpty() ) {
390 continue;
391 }
392 //Convert to standard identifier
393 if ( exportVcard ) {
394 if ( identifier == QLatin1String( "X-messaging/aim-All" ) ) {
395 identifier = QLatin1String( "X-AIM" );
396 } else if ( identifier == QLatin1String( "X-messaging/icq-All" ) ) {
397 identifier = QLatin1String( "X-ICQ" );
398 } else if ( identifier == QLatin1String( "X-messaging/xmpp-All" ) ) {
399 identifier = QLatin1String( "X-JABBER" );
400 } else if ( identifier == QLatin1String( "X-messaging/msn-All" ) ) {
401 identifier = QLatin1String( "X-MSN" );
402 } else if ( identifier == QLatin1String( "X-messaging/yahoo-All" ) ) {
403 identifier = QLatin1String( "X-YAHOO" );
404 } else if ( identifier == QLatin1String( "X-messaging/gadu-All" ) ) {
405 identifier = QLatin1String( "X-GADUGADU" );
406 } else if ( identifier == QLatin1String( "X-messaging/skype-All" ) ) {
407 identifier = QLatin1String( "X-SKYPE" );
408 } else if ( identifier == QLatin1String( "X-messaging/groupwise-All" ) ) {
409 identifier = QLatin1String( "X-GROUPWISE" );
410 } else if ( identifier == QLatin1String( "X-messaging/sms-All" ) ) {
411 identifier = QLatin1String( "X-SMS" );
412 } else if ( identifier == QLatin1String( "X-messaging/meanwhile-All" ) ) {
413 identifier = QLatin1String( "X-MEANWHILE" );
414 } else if ( identifier == QLatin1String( "X-messaging/irc-All" ) ) {
415 identifier = QLatin1String( "X-IRC" ); //Not defined by rfc but need for fixing #300869
416 } else if ( identifier == QLatin1String( "X-messaging/googletalk-All" ) ) {
417 //Not defined by rfc but need for fixing #300869
418 identifier = QLatin1String( "X-GOOGLETALK" );
419 }
420 }
421
422 VCardLine line( identifier, value );
423 if ( version == VCard::v2_1 && needsEncoding( value ) ) {
424 line.addParameter( QLatin1String( "charset" ), QLatin1String( "UTF-8" ) );
425 line.addParameter( QLatin1String( "encoding" ), QLatin1String( "QUOTED-PRINTABLE" ) );
426 }
427 card.addLine( line );
428 }
429
430 vCardList.append( card );
431 }
432
433 return VCardParser::createVCards( vCardList );
434}
435
436Addressee::List VCardTool::parseVCards( const QByteArray &vcard ) const
437{
438 static const QLatin1Char semicolonSep( ';' );
439 static const QLatin1Char commaSep( ',' );
440 QString identifier;
441
442 Addressee::List addrList;
443 const VCard::List vCardList = VCardParser::parseVCards( vcard );
444
445 VCard::List::ConstIterator cardIt;
446 VCard::List::ConstIterator listEnd( vCardList.end() );
447 for ( cardIt = vCardList.begin(); cardIt != listEnd; ++cardIt ) {
448 Addressee addr;
449
450 const QStringList idents = ( *cardIt ).identifiers();
451 QStringList::ConstIterator identIt;
452 QStringList::ConstIterator identEnd( idents.end() );
453 for ( identIt = idents.begin(); identIt != identEnd; ++identIt ) {
454 const VCardLine::List lines = ( *cardIt ).lines( ( *identIt ) );
455 VCardLine::List::ConstIterator lineIt;
456
457 // iterate over the lines
458 for ( lineIt = lines.begin(); lineIt != lines.end(); ++lineIt ) {
459 identifier = ( *lineIt ).identifier().toLower();
460 // ADR
461 if ( identifier == QLatin1String( "adr" ) ) {
462 Address address;
463 const QStringList addrParts = splitString( semicolonSep, ( *lineIt ).value().toString() );
464 if ( addrParts.count() > 0 ) {
465 address.setPostOfficeBox( addrParts.at(0) );
466 }
467 if ( addrParts.count() > 1 ) {
468 address.setExtended( addrParts.at(1) );
469 }
470 if ( addrParts.count() > 2 ) {
471 address.setStreet( addrParts.at(2) );
472 }
473 if ( addrParts.count() > 3 ) {
474 address.setLocality( addrParts.at(3) );
475 }
476 if ( addrParts.count() > 4 ) {
477 address.setRegion( addrParts.at(4) );
478 }
479 if ( addrParts.count() > 5 ) {
480 address.setPostalCode( addrParts.at(5) );
481 }
482 if ( addrParts.count() > 6 ) {
483 address.setCountry( addrParts.at(6) );
484 }
485
486 Address::Type type;
487
488 const QStringList types = ( *lineIt ).parameters( QLatin1String( "type" ) );
489 QStringList::ConstIterator end(types.end());
490 for ( QStringList::ConstIterator it = types.begin(); it != end; ++it ) {
491 type |= mAddressTypeMap[ ( *it ).toLower() ];
492 }
493
494 address.setType( type );
495 if ( !( *lineIt ).parameter( QLatin1String( "label" ) ).isEmpty() ) {
496 address.setLabel( ( *lineIt ).parameter( QLatin1String( "label" ) ) );
497 }
498 addr.insertAddress( address );
499 }
500
501 // BDAY
502 else if ( identifier == QLatin1String( "bday" ) ) {
503 addr.setBirthday( parseDateTime( ( *lineIt ).value().toString() ) );
504 }
505
506 // CATEGORIES
507 else if ( identifier == QLatin1String( "categories" ) ) {
508 const QStringList categories = splitString( commaSep, ( *lineIt ).value().toString() );
509 addr.setCategories( categories );
510 }
511
512 // CLASS
513 else if ( identifier == QLatin1String( "class" ) ) {
514 addr.setSecrecy( parseSecrecy( *lineIt ) );
515 }
516
517 // EMAIL
518 else if ( identifier == QLatin1String( "email" ) ) {
519 const QStringList types = ( *lineIt ).parameters( QLatin1String( "type" ) );
520 addr.insertEmail( ( *lineIt ).value().toString(),
521 types.contains( QLatin1String( "PREF" ) ) );
522 }
523
524 // FN
525 else if ( identifier == QLatin1String( "fn" ) ) {
526 addr.setFormattedName( ( *lineIt ).value().toString() );
527 }
528
529 // GEO
530 else if ( identifier == QLatin1String( "geo" ) ) {
531 Geo geo;
532
533 const QStringList geoParts =
534 ( *lineIt ).value().toString().split( QLatin1Char( ';' ), QString::KeepEmptyParts );
535 if ( geoParts.size() >= 2 ) {
536 geo.setLatitude( geoParts.at( 0 ).toFloat() );
537 geo.setLongitude( geoParts.at( 1 ).toFloat() );
538 addr.setGeo( geo );
539 }
540 }
541
542 // KEY
543 else if ( identifier == QLatin1String( "key" ) ) {
544 addr.insertKey( parseKey( *lineIt ) );
545 }
546
547 // LABEL
548 else if ( identifier == QLatin1String( "label" ) ) {
549 Address::Type type;
550
551 const QStringList types = ( *lineIt ).parameters( QLatin1String( "type" ) );
552 QStringList::ConstIterator end(types.end());
553 for ( QStringList::ConstIterator it = types.begin(); it != end; ++it ) {
554 type |= mAddressTypeMap[ ( *it ).toLower() ];
555 }
556
557 bool available = false;
558 KABC::Address::List addressList = addr.addresses();
559 for ( KABC::Address::List::Iterator it = addressList.begin();
560 it != addressList.end(); ++it ) {
561 if ( ( *it ).type() == type ) {
562 ( *it ).setLabel( ( *lineIt ).value().toString() );
563 addr.insertAddress( *it );
564 available = true;
565 break;
566 }
567 }
568
569 if ( !available ) { // a standalone LABEL tag
570 KABC::Address address( type );
571 address.setLabel( ( *lineIt ).value().toString() );
572 addr.insertAddress( address );
573 }
574 }
575
576 // LOGO
577 else if ( identifier == QLatin1String( "logo" ) ) {
578 addr.setLogo( parsePicture( *lineIt ) );
579 }
580
581 // MAILER
582 else if ( identifier == QLatin1String( "mailer" ) ) {
583 addr.setMailer( ( *lineIt ).value().toString() );
584 }
585
586 // N
587 else if ( identifier == QLatin1String( "n" ) ) {
588 const QStringList nameParts = splitString( semicolonSep, ( *lineIt ).value().toString() );
589 const int numberOfParts( nameParts.count() );
590 if ( numberOfParts > 0 ) {
591 addr.setFamilyName( nameParts.at( 0 ) );
592 }
593 if ( numberOfParts > 1 ) {
594 addr.setGivenName( nameParts.at( 1 ) );
595 }
596 if ( numberOfParts > 2 ) {
597 addr.setAdditionalName( nameParts.at( 2 ) );
598 }
599 if ( numberOfParts > 3 ) {
600 addr.setPrefix( nameParts.at( 3 ) );
601 }
602 if ( numberOfParts > 4 ) {
603 addr.setSuffix( nameParts.at( 4 ) );
604 }
605 if ( !( *lineIt ).parameter( QLatin1String( "sort-as" ) ).isEmpty() ) {
606 addr.setSortString( ( *lineIt ).parameter( QLatin1String( "sort-as" ) ) );
607 }
608 }
609
610 // NAME
611 else if ( identifier == QLatin1String( "name" ) ) {
612 addr.setName( ( *lineIt ).value().toString() );
613 }
614
615 // NICKNAME
616 else if ( identifier == QLatin1String( "nickname" ) ) {
617 addr.setNickName( ( *lineIt ).value().toString() );
618 }
619
620 // NOTE
621 else if ( identifier == QLatin1String( "note" ) ) {
622 addr.setNote( ( *lineIt ).value().toString() );
623 }
624
625 // ORGANIZATION
626 else if ( identifier == QLatin1String( "org" ) ) {
627 const QStringList orgParts = splitString( semicolonSep, ( *lineIt ).value().toString() );
628 if ( orgParts.count() > 0 ) {
629 addr.setOrganization( orgParts.at( 0 ) );
630 }
631 if ( orgParts.count() > 1 ) {
632 addr.setDepartment( orgParts.at( 1 ) );
633 }
634 if ( !( *lineIt ).parameter( QLatin1String( "sort-as" ) ).isEmpty() ) {
635 addr.setSortString( ( *lineIt ).parameter( QLatin1String( "sort-as" ) ) );
636 }
637 }
638
639 // PHOTO
640 else if ( identifier == QLatin1String( "photo" ) ) {
641 addr.setPhoto( parsePicture( *lineIt ) );
642 }
643
644 // PROID
645 else if ( identifier == QLatin1String( "prodid" ) ) {
646 addr.setProductId( ( *lineIt ).value().toString() );
647 }
648
649 // REV
650 else if ( identifier == QLatin1String( "rev" ) ) {
651 addr.setRevision( parseDateTime( ( *lineIt ).value().toString() ) );
652 }
653
654 // ROLE
655 else if ( identifier == QLatin1String( "role" ) ) {
656 addr.setRole( ( *lineIt ).value().toString() );
657 }
658
659 // SORT-STRING
660 else if ( identifier == QLatin1String( "sort-string" ) ) {
661 addr.setSortString( ( *lineIt ).value().toString() );
662 }
663
664 // SOUND
665 else if ( identifier == QLatin1String( "sound" ) ) {
666 addr.setSound( parseSound( *lineIt ) );
667 }
668
669 // TEL
670 else if ( identifier == QLatin1String( "tel" ) ) {
671 PhoneNumber phone;
672 phone.setNumber( ( *lineIt ).value().toString() );
673
674 PhoneNumber::Type type;
675
676 const QStringList types = ( *lineIt ).parameters( QLatin1String( "type" ) );
677 QStringList::ConstIterator typeEnd( types.end() );
678 for ( QStringList::ConstIterator it = types.begin(); it != typeEnd; ++it ) {
679 type |= mPhoneTypeMap[( *it ).toUpper()];
680 }
681
682 phone.setType( type );
683
684 addr.insertPhoneNumber( phone );
685 }
686
687 // TITLE
688 else if ( identifier == QLatin1String( "title" ) ) {
689 addr.setTitle( ( *lineIt ).value().toString() );
690 }
691
692 // TZ
693 else if ( identifier == QLatin1String( "tz" ) ) {
694 TimeZone tz;
695 const QString date = ( *lineIt ).value().toString();
696
697 if ( !date.isEmpty() ) {
698 int hours = date.mid( 1, 2 ).toInt();
699 int minutes = date.mid( 4, 2 ).toInt();
700 int offset = ( hours * 60 ) + minutes;
701 offset = offset * ( date[ 0 ] == QLatin1Char( '+' ) ? 1 : -1 );
702
703 tz.setOffset( offset );
704 addr.setTimeZone( tz );
705 }
706 }
707
708 // UID
709 else if ( identifier == QLatin1String( "uid" ) ) {
710 addr.setUid( ( *lineIt ).value().toString() );
711 }
712
713 // URL
714 else if ( identifier == QLatin1String( "url" ) ) {
715 addr.setUrl( KUrl( ( *lineIt ).value().toString() ) );
716 }
717
718 // X-
719 else if ( identifier.startsWith( QLatin1String( "x-" ) ) ) {
720 QString ident = ( *lineIt ).identifier();
721 //X-Evolution
722 if ( identifier == QLatin1String( "x-evolution-spouse" ) ||
723 identifier == QLatin1String( "x-spouse" ) ) {
724 ident = QLatin1String( "X-KADDRESSBOOK-X-SpousesName" );
725 } else if ( identifier == QLatin1String( "x-evolution-blog-url" ) ) {
726 ident = QLatin1String( "X-KADDRESSBOOK-BlogFeed" );
727 } else if ( identifier == QLatin1String( "x-evolution-assistant" ) ||
728 identifier == QLatin1String( "x-assistant" ) ) {
729 ident = QLatin1String( "X-KADDRESSBOOK-X-AssistantsName" );
730 } else if ( identifier == QLatin1String( "x-evolution-anniversary" ) ||
731 identifier == QLatin1String( "x-anniversary" ) ) {
732 ident = QLatin1String( "X-KADDRESSBOOK-X-Anniversary" );
733 } else if ( identifier == QLatin1String( "x-evolution-manager" ) ||
734 identifier == QLatin1String( "x-manager" ) ) {
735 ident = QLatin1String( "X-KADDRESSBOOK-X-ManagersName" );
736 } else if ( identifier == QLatin1String( "x-aim" ) ) {
737 ident = QLatin1String( "X-messaging/aim-All" );
738 } else if ( identifier == QLatin1String( "x-icq" ) ) {
739 ident = QLatin1String( "X-messaging/icq-All" );
740 } else if ( identifier == QLatin1String( "x-jabber" ) ) {
741 ident = QLatin1String( "X-messaging/xmpp-All" );
742 } else if ( identifier == QLatin1String( "x-jabber" ) ) {
743 ident = QLatin1String( "X-messaging/xmpp-All" );
744 } else if ( identifier == QLatin1String( "x-msn" ) ) {
745 ident = QLatin1String( "X-messaging/msn-All" );
746 } else if ( identifier == QLatin1String( "x-yahoo" ) ) {
747 ident = QLatin1String( "X-messaging/yahoo-All" );
748 } else if ( identifier == QLatin1String( "x-gadugadu" ) ) {
749 ident = QLatin1String( "X-messaging/gadu-All" );
750 } else if ( identifier == QLatin1String( "x-skype" ) ) {
751 ident = QLatin1String( "X-messaging/skype-All" );
752 } else if ( identifier == QLatin1String( "x-groupwise" ) ) {
753 ident = QLatin1String( "X-messaging/groupwise-All" );
754 } else if ( identifier == QLatin1String( "x-sms" ) ) {
755 ident = QLatin1String( "X-messaging/sms-All" );
756 } else if ( identifier == QLatin1String( "x-meanwhile" ) ) {
757 ident = QLatin1String( "X-messaging/meanwhile-All" );
758 } else if ( identifier == QLatin1String( "x-irc" ) ) {
759 ident = QLatin1String( "X-messaging/irc-All" );
760 } else if ( identifier == QLatin1String( "x-googletalk" ) ) {
761 ident = QLatin1String( "X-messaging/googletalk-All" );
762 }
763
764 const QString key = ident.mid( 2 );
765 const int dash = key.indexOf( QLatin1Char( '-' ) );
766 addr.insertCustom( key.left( dash ), key.mid( dash + 1 ),
767 ( *lineIt ).value().toString() );
768 }
769 }
770 }
771
772 addrList.append( addr );
773 }
774
775 return addrList;
776}
777
778QDateTime VCardTool::parseDateTime( const QString &str ) const
779{
780 QDate date;
781 QTime time;
782
783 if ( str.indexOf( QLatin1Char( '-' ) ) == -1 ) { // is base format (yyyymmdd)
784 date = QDate( str.left( 4 ).toInt(), str.mid( 4, 2 ).toInt(),
785 str.mid( 6, 2 ).toInt() );
786 } else { // is extended format yyyy-mm-dd
787 date = QDate( str.left( 4 ).toInt(), str.mid( 5, 2 ).toInt(),
788 str.mid( 8, 2 ).toInt() );
789 }
790
791 // does it also contain a time ? (Note: mm, ss are optional according ISO-8601)
792 int timeStart = str.indexOf( QLatin1Char( 'T' ) );
793 if ( timeStart >= 0 ) {
794 int hour = 0, minute = 0, second = 0;
795
796 hour = str.mid( timeStart + 1, 2 ).toInt(); // hour must always be given
797
798 if ( str.indexOf( QLatin1Char( ':' ), timeStart + 1 ) > 0 ) { // extended format (hh:mm:ss)
799 if ( str.length() >= ( timeStart + 5 ) ) {
800 minute = str.mid( timeStart + 4, 2 ).toInt();
801 if ( str.length() >= ( timeStart + 8 ) ) {
802 second = str.mid( timeStart + 7, 2 ).toInt();
803 }
804 }
805 } else { // basic format (hhmmss)
806 if ( str.length() >= ( timeStart + 4 ) ) {
807 minute = str.mid( timeStart + 3, 2 ).toInt();
808 if ( str.length() >= ( timeStart + 6 ) ) {
809 second = str.mid( timeStart + 5, 2 ).toInt();
810 }
811 }
812 }
813
814 time = QTime( hour, minute, second );
815 }
816
817 Qt::TimeSpec spec = ( str.right( 1 ) == QLatin1String( "Z" ) ) ? Qt::UTC : Qt::LocalTime;
818
819 QDateTime dateTime( date );
820
821 // explicitly set the time, which might be invalid, to keep the information
822 // that the time is invalid. In createDateTime() the time/invalid flag is
823 // checked which omits then to print the timestamp
824 // This is needed to reproduce the given string in input
825 // e.g. BDAY:2008-12-30
826 // without time shall also result in a string without a time
827 dateTime.setTime( time );
828
829 dateTime.setTimeSpec( spec );
830 return dateTime;
831}
832
833QString VCardTool::createDateTime( const QDateTime &dateTime ) const
834{
835 QString str;
836
837 if ( dateTime.date().isValid() ) {
838 str.sprintf( "%4d-%02d-%02d", dateTime.date().year(), dateTime.date().month(),
839 dateTime.date().day() );
840 if ( dateTime.time().isValid() ) {
841 QString tmp;
842 tmp.sprintf( "T%02d:%02d:%02d", dateTime.time().hour(), dateTime.time().minute(),
843 dateTime.time().second() );
844 str += tmp;
845
846 if ( dateTime.timeSpec() == Qt::UTC ) {
847 str += QLatin1Char( 'Z' );
848 }
849 }
850 }
851
852 return str;
853}
854
855Picture VCardTool::parsePicture( const VCardLine &line ) const
856{
857 Picture pic;
858
859 const QStringList params = line.parameterList();
860 QString type;
861 if ( params.contains( QLatin1String( "type" ) ) ) {
862 type = line.parameter( QLatin1String( "type" ) );
863 }
864 if ( params.contains( QLatin1String( "encoding" ) ) ) {
865 pic.setRawData( line.value().toByteArray(), type );
866 } else if ( params.contains( QLatin1String( "value" ) ) ) {
867 if ( line.parameter( QLatin1String( "value" ) ).toLower() == QLatin1String( "uri" ) ) {
868 pic.setUrl( line.value().toString() );
869 }
870 }
871
872 return pic;
873}
874
875VCardLine VCardTool::createPicture( const QString &identifier, const Picture &pic ) const
876{
877 VCardLine line( identifier );
878
879 if ( pic.isEmpty() ) {
880 return line;
881 }
882
883 if ( pic.isIntern() ) {
884 line.setValue( pic.rawData() );
885 line.addParameter( QLatin1String( "encoding" ), QLatin1String( "b" ) );
886 line.addParameter( QLatin1String( "type" ), pic.type() );
887 } else {
888 line.setValue( pic.url() );
889 line.addParameter( QLatin1String( "value" ), QLatin1String( "URI" ) );
890 }
891
892 return line;
893}
894
895Sound VCardTool::parseSound( const VCardLine &line ) const
896{
897 Sound snd;
898
899 const QStringList params = line.parameterList();
900 if ( params.contains( QLatin1String( "encoding" ) ) ) {
901 snd.setData( line.value().toByteArray() );
902 } else if ( params.contains( QLatin1String( "value" ) ) ) {
903 if ( line.parameter( QLatin1String( "value" ) ).toLower() == QLatin1String( "uri" ) ) {
904 snd.setUrl( line.value().toString() );
905 }
906 }
907
908/* TODO: support sound types
909 if ( params.contains( "type" ) )
910 snd.setType( line.parameter( "type" ) );
911*/
912
913 return snd;
914}
915
916VCardLine VCardTool::createSound( const Sound &snd ) const
917{
918 VCardLine line( QLatin1String( "SOUND" ) );
919
920 if ( snd.isIntern() ) {
921 if ( !snd.data().isEmpty() ) {
922 line.setValue( snd.data() );
923 line.addParameter( QLatin1String( "encoding" ), QLatin1String( "b" ) );
924 // TODO: need to store sound type!!!
925 }
926 } else if ( !snd.url().isEmpty() ) {
927 line.setValue( snd.url() );
928 line.addParameter( QLatin1String( "value" ), QLatin1String( "URI" ) );
929 }
930
931 return line;
932}
933
934Key VCardTool::parseKey( const VCardLine &line ) const
935{
936 Key key;
937
938 const QStringList params = line.parameterList();
939 if ( params.contains( QLatin1String( "encoding" ) ) ) {
940 key.setBinaryData( line.value().toByteArray() );
941 } else {
942 key.setTextData( line.value().toString() );
943 }
944
945 if ( params.contains( QLatin1String( "type" ) ) ) {
946 if ( line.parameter( QLatin1String( "type" ) ).toLower() == QLatin1String( "x509" ) ) {
947 key.setType( Key::X509 );
948 } else if ( line.parameter( QLatin1String( "type" ) ).toLower() == QLatin1String( "pgp" ) ) {
949 key.setType( Key::PGP );
950 } else {
951 key.setType( Key::Custom );
952 key.setCustomTypeString( line.parameter( QLatin1String( "type" ) ) );
953 }
954 }
955
956 return key;
957}
958
959VCardLine VCardTool::createKey( const Key &key ) const
960{
961 VCardLine line( QLatin1String( "KEY" ) );
962
963 if ( key.isBinary() ) {
964 if ( !key.binaryData().isEmpty() ) {
965 line.setValue( key.binaryData() );
966 line.addParameter( QLatin1String( "encoding" ), QLatin1String( "b" ) );
967 }
968 } else if ( !key.textData().isEmpty() ) {
969 line.setValue( key.textData() );
970 }
971
972 if ( key.type() == Key::X509 ) {
973 line.addParameter( QLatin1String( "type" ), QLatin1String( "X509" ) );
974 } else if ( key.type() == Key::PGP ) {
975 line.addParameter( QLatin1String( "type" ), QLatin1String( "PGP" ) );
976 } else if ( key.type() == Key::Custom ) {
977 line.addParameter( QLatin1String( "type" ), key.customTypeString() );
978 }
979
980 return line;
981}
982
983Secrecy VCardTool::parseSecrecy( const VCardLine &line ) const
984{
985 Secrecy secrecy;
986
987 const QString value = line.value().toString().toLower();
988 if ( value == QLatin1String( "public" ) ) {
989 secrecy.setType( Secrecy::Public );
990 } else if ( value == QLatin1String( "private" ) ) {
991 secrecy.setType( Secrecy::Private );
992 } else if ( value == QLatin1String( "confidential" ) ) {
993 secrecy.setType( Secrecy::Confidential );
994 }
995
996 return secrecy;
997}
998
999VCardLine VCardTool::createSecrecy( const Secrecy &secrecy ) const
1000{
1001 VCardLine line( QLatin1String( "CLASS" ) );
1002
1003 int type = secrecy.type();
1004
1005 if ( type == Secrecy::Public ) {
1006 line.setValue( QLatin1String( "PUBLIC" ) );
1007 } else if ( type == Secrecy::Private ) {
1008 line.setValue( QLatin1String( "PRIVATE" ) );
1009 } else if ( type == Secrecy::Confidential ) {
1010 line.setValue( QLatin1String( "CONFIDENTIAL" ) );
1011 }
1012
1013 return line;
1014}
1015
1016QStringList VCardTool::splitString( const QChar &sep, const QString &str ) const
1017{
1018 QStringList list;
1019 QString value( str );
1020
1021 int start = 0;
1022 int pos = value.indexOf( sep, start );
1023
1024 while ( pos != -1 ) {
1025 if ( pos == 0 || value[ pos - 1 ] != QLatin1Char( '\\' ) ) {
1026 if ( pos > start && pos <= (int)value.length() ) {
1027 list << value.mid( start, pos - start );
1028 } else {
1029 list << QString();
1030 }
1031
1032 start = pos + 1;
1033 pos = value.indexOf( sep, start );
1034 } else {
1035 value.replace( pos - 1, 2, sep );
1036 pos = value.indexOf( sep, pos );
1037 }
1038 }
1039
1040 int l = value.length() - 1;
1041 if ( value.mid( start, l - start + 1 ).length() > 0 ) {
1042 list << value.mid( start, l - start + 1 );
1043 } else {
1044 list << QString();
1045 }
1046
1047 return list;
1048}
1049