1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtVersit module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL21$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 or version 3 as published by the Free
20** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22** following information to ensure the GNU Lesser General Public License
23** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25**
26** As a special exception, The Qt Company gives you certain additional
27** rights. These rights are described in The Qt Company LGPL Exception
28** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29**
30** $QT_END_LICENSE$
31**
32****************************************************************************/
33
34#include "qversitcontactimporter_p.h"
35
36#include <QtContacts/qcontactdetails.h>
37
38#include "qversitcontacthandler.h"
39#include "qversitcontactpluginloader_p.h"
40#include "qversitcontactsdefs_p.h"
41#include "qversitdocument.h"
42#include "qversitpluginsearch_p.h"
43#include "qversitproperty.h"
44#include "qversitutils_p.h"
45
46QTCONTACTS_USE_NAMESPACE
47
48QT_BEGIN_NAMESPACE_VERSIT
49
50/*!
51 * Constructor.
52 */
53QVersitContactImporterPrivate::QVersitContactImporterPrivate(const QStringList& profiles) :
54 mPropertyHandler(NULL),
55 mPropertyHandler2(NULL),
56 mPropertyHandlerVersion(0),
57 mDefaultResourceHandler(new QVersitDefaultResourceHandler),
58 mResourceHandler(mDefaultResourceHandler)
59{
60 // Contact detail mappings
61 int versitPropertyCount =
62 sizeof(versitContactDetailMappings)/sizeof(VersitContactDetailMapping);
63 for (int i=0; i < versitPropertyCount; i++) {
64 QString versitPropertyName =
65 QLatin1Literal(versitContactDetailMappings[i].versitPropertyName);
66 QPair<QContactDetail::DetailType, int> contactDetail;
67 contactDetail.first =
68 versitContactDetailMappings[i].detailType;
69 contactDetail.second =
70 versitContactDetailMappings[i].detailField;
71 mDetailMappings.insert(akey: versitPropertyName,avalue: contactDetail);
72 }
73
74 // Context mappings
75 int contextCount = sizeof(versitContextMappings)/sizeof(VersitContextMapping);
76 for (int i=0; i < contextCount; i++) {
77 mContextMappings.insert(
78 akey: versitContextMappings[i].contactContext,
79 avalue: QLatin1String(versitContextMappings[i].versitString));
80 }
81
82 // Subtype mappings (insert in reverse order, so first definition (most recent insertion) is found first)
83 int subTypeCount = sizeof(versitSubTypeMappings)/sizeof(VersitSubTypeMapping);
84 for (int i=(subTypeCount - 1); i >= 0; --i) {
85 mSubTypeMappings.insert(
86 akey: QLatin1String(versitSubTypeMappings[i].versitString),
87 avalue: QPair<QContactDetail::DetailType, int>(
88 versitSubTypeMappings[i].detailType,
89 versitSubTypeMappings[i].contactSubType));
90 }
91
92 mPluginPropertyHandlers = QVersitContactPluginLoader::instance()->createContactHandlers(profiles);
93}
94
95/*!
96 * Destructor.
97 */
98QVersitContactImporterPrivate::~QVersitContactImporterPrivate()
99{
100 delete mDefaultResourceHandler;
101 foreach (QVersitContactHandler* pluginHandler, mPluginPropertyHandlers) {
102 delete pluginHandler;
103 }
104}
105
106/*!
107 * Generates a QContact from \a versitDocument.
108 */
109bool QVersitContactImporterPrivate::importContact(
110 const QVersitDocument& document, int contactIndex, QContact* contact,
111 QVersitContactImporter::Error* error)
112{
113 if (document.componentType() != QStringLiteral("VCARD")
114 && document.type() != QVersitDocument::VCard21Type
115 && document.type() != QVersitDocument::VCard30Type) {
116 *error = QVersitContactImporter::InvalidDocumentError;
117 return false;
118 }
119 const QList<QVersitProperty> properties = document.properties();
120 if (properties.size() == 0) {
121 *error = QVersitContactImporter::EmptyDocumentError;
122 return false;
123 }
124
125 // First, do the properties with PREF set so they appear first in the contact details
126 foreach (const QVersitProperty& property, properties) {
127 QStringList typeParameters = property.parameters().values(QStringLiteral("TYPE"));
128 if (typeParameters.contains(QStringLiteral("PREF"), cs: Qt::CaseInsensitive))
129 importProperty(document, property, contactIndex, contact);
130 }
131 // ... then, do the rest of the properties.
132 foreach (const QVersitProperty& property, properties) {
133 QStringList typeParameters = property.parameters().values(QStringLiteral("TYPE"));
134 if (!typeParameters.contains(QStringLiteral("PREF"), cs: Qt::CaseInsensitive))
135 importProperty(document, property, contactIndex, contact);
136 }
137
138 contact->setType(QContactType::TypeContact);
139
140 mRestoreHandler.documentProcessed();
141 // run plugin handlers
142 foreach (QVersitContactImporterPropertyHandlerV2* handler, mPluginPropertyHandlers) {
143 handler->documentProcessed(document, contact);
144 }
145 // run the v2 handler, if set
146 if (mPropertyHandler2 && mPropertyHandlerVersion > 1) {
147 mPropertyHandler2->documentProcessed(document, contact);
148 }
149
150 return true;
151}
152
153void QVersitContactImporterPrivate::importProperty(
154 const QVersitDocument& document, const QVersitProperty& property, int contactIndex,
155 QContact* contact)
156{
157 if (mPropertyHandler
158 && mPropertyHandlerVersion == 1
159 && mPropertyHandler->preProcessProperty(document, property, contactIndex, contact))
160 return;
161
162 QPair<QContactDetail::DetailType, int> detailDefinition =
163 mDetailMappings.value(akey: property.name());
164 QContactDetail::DetailType detailType = detailDefinition.first;
165
166 QList<QContactDetail> updatedDetails;
167
168 bool success = false;
169
170 // The following functions create and save the details to the contact
171 switch (detailType) {
172 case QContactDetail::TypeAddress:
173 success = createAddress(property, contact, updatedDetails: &updatedDetails); // pass in group
174 break;
175 case QContactDetail::TypeAnniversary:
176 success = createAnniversary(property, contact, updatedDetails: &updatedDetails);
177 break;
178 case QContactDetail::TypeAvatar:
179 success = createAvatar(property, contact, updatedDetails: &updatedDetails);
180 break;
181 case QContactDetail::TypeBirthday:
182 success = createBirthday(property, contact, updatedDetails: &updatedDetails);
183 break;
184 case QContactDetail::TypeExtendedDetail:
185 success = createExtendedDetail(property, contact, updatedDetails: &updatedDetails);
186 break;
187 case QContactDetail::TypeFamily:
188 success = createFamily(property, contact, updatedDetails: &updatedDetails);
189 break;
190 case QContactDetail::TypeFavorite:
191 success = createFavorite(property, contact, updatedDetails: &updatedDetails);
192 break;
193 case QContactDetail::TypeGender:
194 success = createGender(property, contact, updatedDetails: &updatedDetails);
195 break;
196 case QContactDetail::TypeGeoLocation:
197 success = createGeoLocation(property, contact, updatedDetails: &updatedDetails);
198 break;
199 case QContactDetail::TypeName:
200 success = createName(property, contact, updatedDetails: &updatedDetails);
201 break;
202 case QContactDetail::TypeNickname:
203 success = createNicknames(property, contact, updatedDetails: &updatedDetails);
204 break;
205 case QContactDetail::TypeDisplayLabel:
206 success = createDisplaylabel(property, contact, updatedDetails: &updatedDetails);
207 break;
208 case QContactDetail::TypeOnlineAccount:
209 success = createOnlineAccount(property, contact, updatedDetails: &updatedDetails);
210 break;
211 case QContactDetail::TypeOrganization:
212 success = createOrganization(property, contact, updatedDetails: &updatedDetails);
213 break;
214 case QContactDetail::TypePhoneNumber:
215 success = createPhone(property, contact, updatedDetails: &updatedDetails);
216 break;
217 case QContactDetail::TypeRingtone:
218 success = createRingtone(property, contact, updatedDetails: &updatedDetails);
219 break;
220 case QContactDetail::TypeTag:
221 success = createTags(property, contact, updatedDetails: &updatedDetails);
222 break;
223 case QContactDetail::TypeTimestamp:
224 success = createTimeStamp(property, contact, updatedDetails: &updatedDetails);
225 break;
226 case QContactDetail::TypeVersion:
227 success = createVersion(property, contact, updatedDetails: &updatedDetails);
228 break;
229 default:
230 // Look up mDetailMappings for a simple mapping from property to detail.
231 success = createNameValueDetail(property, contact, updatedDetails: &updatedDetails);
232 break;
233 }
234
235 if (mRestoreHandler.propertyProcessed(property, updatedDetails: &updatedDetails))
236 success = true;
237
238 // run plugin handlers
239 foreach (QVersitContactImporterPropertyHandlerV2* handler, mPluginPropertyHandlers) {
240 handler->propertyProcessed(document, property, contact: *contact, alreadyProcessed: &success, updatedDetails: &updatedDetails);
241 }
242 // run the v2 handler, if set
243 if (mPropertyHandler2 && mPropertyHandlerVersion > 1) {
244 mPropertyHandler2->propertyProcessed(document, property, contact: *contact, alreadyProcessed: &success, updatedDetails: &updatedDetails);
245 }
246
247 foreach (QContactDetail detail, updatedDetails) {
248 contact->saveDetail(detail: &detail);
249 }
250
251 // run the v1 handler, if set
252 if (mPropertyHandler && mPropertyHandlerVersion == 1)
253 mPropertyHandler->postProcessProperty(document, property, alreadyProcessed: success, contactIndex, contact);
254}
255/*!
256 * Creates a QContactName from \a property
257 */
258bool QVersitContactImporterPrivate::createName(
259 const QVersitProperty& property,
260 QContact* contact,
261 QList<QContactDetail>* updatedDetails)
262{
263 QContactName name;
264 QContactDetail detail = contact->detail(type: QContactName::Type);
265 if (!detail.isEmpty()) {
266 // If multiple name properties exist,
267 // discard all except the first occurrence
268 if (!detail.value(field: QContactName::FieldFirstName).toString().isEmpty())
269 return false;
270 else
271 name = QContactName(static_cast<QContactName>(detail));
272 }
273
274 QVariant variant = property.variantValue();
275 if (property.valueType() != QVersitProperty::CompoundType
276 || variant.type() != QVariant::StringList)
277 return false;
278 QStringList values = variant.toStringList();
279 QString value(takeFirst(list&: values));
280 if (!value.isEmpty())
281 name.setLastName(value);
282 value = takeFirst(list&: values);
283 if (!value.isEmpty())
284 name.setFirstName(value);
285 value = takeFirst(list&: values);
286 if (!value.isEmpty())
287 name.setMiddleName(value);
288 value = takeFirst(list&: values);
289 if (!value.isEmpty())
290 name.setPrefix(value);
291 value = takeFirst(list&: values);
292 if (!value.isEmpty())
293 name.setSuffix(value);
294
295 saveDetailWithContext(updatedDetails, detail: name, contexts: extractContexts(property));
296 return true;
297}
298
299/*!
300 * Creates a QContactPhoneNumber from \a property
301 */
302bool QVersitContactImporterPrivate::createPhone(
303 const QVersitProperty& property,
304 QContact* contact,
305 QList<QContactDetail>* updatedDetails)
306{
307 Q_UNUSED(contact)
308 QContactPhoneNumber phone;
309 QString value(property.value());
310 if (value.isEmpty())
311 return false;
312 phone.setNumber(property.value());
313 QStringList subTypes(extractSubTypes(property));
314 QList<int> subTypesInt;
315
316 foreach (const QString &stringValue, subTypes) {
317 if (!mContextMappings.values().contains(t: stringValue)) {
318 QMultiHash<QString, QPair<QContactDetail::DetailType, int> >::const_iterator
319 it = mSubTypeMappings.constFind(akey: stringValue);
320 if (it != mSubTypeMappings.constEnd()) {
321 int mappedValue = it.value().second;
322 subTypesInt << mappedValue;
323 }
324 }
325 }
326
327 if (property.name() == QStringLiteral("X-ASSISTANT-TEL"))
328 subTypesInt << QContactPhoneNumber::SubTypeAssistant;
329 if (!subTypesInt.isEmpty())
330 phone.setSubTypes(subTypesInt);
331 saveDetailWithContext(updatedDetails, detail: phone, contexts: extractContexts(property));
332 return true;
333}
334
335/*!
336 * Creates a QContactAddress from \a property
337 */
338bool QVersitContactImporterPrivate::createAddress(
339 const QVersitProperty& property,
340 QContact* contact,
341 QList<QContactDetail>* updatedDetails)
342{
343 Q_UNUSED(contact)
344 QContactAddress address;
345
346 QVariant variant = property.variantValue();
347 if (property.valueType() != QVersitProperty::CompoundType
348 || variant.type() != QVariant::StringList)
349 return false;
350 QStringList addressParts = variant.toStringList();
351 QString value(takeFirst(list&: addressParts));
352 if (!value.isEmpty())
353 address.setPostOfficeBox(value);
354 // There is no setter for the Extended Address in QContactAddress:
355 if (!addressParts.isEmpty())
356 addressParts.removeFirst();
357 value = takeFirst(list&: addressParts);
358 if (!value.isEmpty())
359 address.setStreet(value);
360 value = takeFirst(list&: addressParts);
361 if (!value.isEmpty())
362 address.setLocality(value);
363 value = takeFirst(list&: addressParts);
364 if (!value.isEmpty())
365 address.setRegion(value);
366 value = takeFirst(list&: addressParts);
367 if (!value.isEmpty())
368 address.setPostcode(value);
369 value = takeFirst(list&: addressParts);
370 if (!value.isEmpty())
371 address.setCountry(value);
372 QStringList subTypes(extractSubTypes(property));
373 QList<int> subTypesInt;
374
375 foreach (const QString &stringValue, subTypes) {
376 QMultiHash<QString, QPair<QContactDetail::DetailType, int> >::const_iterator
377 it = mSubTypeMappings.constFind(akey: stringValue);
378 if (it != mSubTypeMappings.constEnd()) {
379 int mappedValue = it.value().second;
380 subTypesInt << mappedValue;
381 }
382 }
383
384 if (!subTypesInt.isEmpty())
385 address.setSubTypes(subTypesInt);
386
387 saveDetailWithContext(updatedDetails, detail: address, contexts: extractContexts(property));
388 return true;
389}
390
391/*!
392 * Creates a QContactOrganization from \a property
393 */
394bool QVersitContactImporterPrivate::createOrganization(
395 const QVersitProperty& property,
396 QContact* contact,
397 QList<QContactDetail>* updatedDetails)
398{
399 QContactOrganization organization;
400 QPair<QContactDetail::DetailType, int> detailTypeAndFieldName =
401 mDetailMappings.value(akey: property.name());
402 int fieldName = detailTypeAndFieldName.second;
403 QList<QContactOrganization> organizations = contact->details<QContactOrganization>();
404 foreach(const QContactOrganization& current, organizations) {
405 if (current.value(field: fieldName).toString().length() == 0) {
406 organization = current;
407 break;
408 }
409 }
410 if (fieldName == QContactOrganization::FieldName) {
411 setOrganizationNames(org&: organization, property);
412 } else if (fieldName == QContactOrganization::FieldTitle) {
413 organization.setTitle(property.value());
414 } else if (fieldName == QContactOrganization::FieldRole) {
415 organization.setRole(property.value());
416 } else if (fieldName == QContactOrganization::FieldLogoUrl) {
417 setOrganizationLogo(org&: organization, property);
418 } else if (fieldName == QContactOrganization::FieldAssistantName) {
419 organization.setAssistantName(property.value());
420 } else {
421 return false;
422 }
423
424 saveDetailWithContext(updatedDetails, detail: organization, contexts: extractContexts(property));
425 return true;
426}
427
428/*!
429 * Set the organization name and department(s) from \a property.
430 */
431void QVersitContactImporterPrivate::setOrganizationNames(
432 QContactOrganization& organization, const QVersitProperty& property) const
433{
434 QVariant variant = property.variantValue();
435 if (property.valueType() == QVersitProperty::CompoundType
436 && variant.type() == QVariant::StringList) {
437 QStringList values = variant.toStringList();
438 QString name(takeFirst(list&: values));
439 if (!name.isEmpty())
440 organization.setName(name);
441 if (!values.isEmpty())
442 organization.setDepartment(values);
443 }
444}
445
446/*!
447 * Set the organization logo from \a property.
448 */
449void QVersitContactImporterPrivate::setOrganizationLogo(
450 QContactOrganization& org, const QVersitProperty& property) const
451{
452 QString location;
453 QByteArray data;
454 saveDataFromProperty(property, location: &location, data: &data);
455 if (!location.isEmpty())
456 org.setLogoUrl(QUrl(location));
457}
458
459/*!
460 * Creates a QContactTimeStamp from \a property
461 */
462bool QVersitContactImporterPrivate::createTimeStamp(
463 const QVersitProperty& property,
464 QContact* contact,
465 QList<QContactDetail>* updatedDetails)
466{
467 Q_UNUSED(contact)
468 QContactTimestamp timeStamp;
469 QString value(property.value());
470 QDateTime dateTime = parseDateTime(text: value);
471 if (!dateTime.isValid())
472 return false;
473 timeStamp.setLastModified(dateTime);
474 saveDetailWithContext(updatedDetails, detail: timeStamp, contexts: extractContexts(property));
475 return true;
476}
477
478/*!
479 * Creates a QContactVersion from \a property
480 */
481bool QVersitContactImporterPrivate::createVersion(
482 const QVersitProperty& property,
483 QContact* contact,
484 QList<QContactDetail>* updatedDetails)
485{
486 // Allow only on version detail, discard others.
487 QContactDetail detail = contact->detail(type: QContactVersion::Type);
488 if (!detail.isEmpty())
489 return false; // Only one version detail is created
490
491 QVariant variant = property.variantValue();
492 if (property.valueType() != QVersitProperty::CompoundType
493 || variant.type() != QVariant::StringList)
494 return false;
495 QStringList values = variant.toStringList();
496 bool ok;
497 QContactVersion version;
498 version.setSequenceNumber(takeFirst(list&: values).toInt(ok: &ok));
499 version.setExtendedVersion(takeFirst(list&: values).toLocal8Bit());
500 if (ok) {
501 saveDetailWithContext(updatedDetails, detail: version, contexts: extractContexts(property));
502 return true;
503 } else {
504 return false;
505 }
506}
507
508/*!
509 * Creates a QContactAnniversary from \a property
510 */
511bool QVersitContactImporterPrivate::createAnniversary(
512 const QVersitProperty& property,
513 QContact* contact,
514 QList<QContactDetail>* updatedDetails)
515{
516 Q_UNUSED(contact)
517 QContactAnniversary anniversary;
518 bool justDate = false;
519 QDateTime dateTime = parseDateTime(text: property.value(), justDate: &justDate);
520 if (!dateTime.isValid())
521 return false;
522 if (justDate)
523 anniversary.setOriginalDate(dateTime.date());
524 else
525 anniversary.setOriginalDateTime(dateTime);
526 saveDetailWithContext(updatedDetails, detail: anniversary, contexts: extractContexts(property));
527 return true;
528}
529
530/*!
531 * Creates a QContactBirthday from \a property
532 */
533bool QVersitContactImporterPrivate::createBirthday(
534 const QVersitProperty& property,
535 QContact* contact,
536 QList<QContactDetail>* updatedDetails)
537{
538 Q_UNUSED(contact)
539 QContactBirthday bday;
540 bool justDate = false;
541 QDateTime dateTime = parseDateTime(text: property.value(), justDate: &justDate);
542 if (!dateTime.isValid())
543 return false;
544 if (justDate)
545 bday.setDate(dateTime.date());
546 else
547 bday.setDateTime(dateTime);
548 saveDetailWithContext(updatedDetails, detail: bday, contexts: extractContexts(property));
549 return true;
550}
551
552/*!
553* Creates a QContactDisplayLabel from \a property
554*/
555bool QVersitContactImporterPrivate::createDisplaylabel(
556 const QVersitProperty& property,
557 QContact* contact,
558 QList<QContactDetail>* updatedDetails)
559{
560 QString label(property.value());
561 if (!label.isEmpty()) {
562 QContactDisplayLabel displayLabel;
563 QContactDisplayLabel existingDisplayLabel = contact->detail<QContactDisplayLabel>();
564 if (!existingDisplayLabel.isEmpty()) {
565 displayLabel = existingDisplayLabel;
566 }
567 displayLabel.setLabel(property.value());
568 saveDetailWithContext(updatedDetails, detail: displayLabel, contexts: extractContexts(property));
569 return true;
570 } else {
571 return false;
572 }
573}
574
575/*!
576 * Creates QContactNicknames from \a property
577 */
578bool QVersitContactImporterPrivate::createNicknames(
579 const QVersitProperty& property,
580 QContact* contact,
581 QList<QContactDetail>* updatedDetails)
582{
583 Q_UNUSED(contact)
584 QVariant variant = property.variantValue();
585 if (property.valueType() != QVersitProperty::ListType
586 || variant.type() != QVariant::StringList)
587 return false;
588 QStringList values = variant.toStringList();
589 QList<int> contexts = extractContexts(property);
590
591 // We don't want to make duplicates of existing nicknames
592 QSet<QString> existingNicknames;
593 foreach (const QContactNickname& nickname, contact->details<QContactNickname>()) {
594 existingNicknames.insert(value: nickname.nickname());
595 }
596 foreach(const QString& value, values) {
597 if (!value.isEmpty() && !existingNicknames.contains(value)) {
598 QContactNickname nickname;
599 nickname.setNickname(value);
600 saveDetailWithContext(updatedDetails, detail: nickname, contexts);
601 existingNicknames.insert(value);
602 }
603 }
604 return true;
605}
606
607/*!
608 * Creates QContactTags from \a property
609 */
610bool QVersitContactImporterPrivate::createTags(
611 const QVersitProperty& property,
612 QContact* contact,
613 QList<QContactDetail>* updatedDetails)
614{
615 Q_UNUSED(contact)
616 QVariant variant = property.variantValue();
617 if (property.valueType() != QVersitProperty::ListType
618 || variant.type() != QVariant::StringList)
619 return false;
620 QStringList values = variant.toStringList();
621 QList<int> contexts = extractContexts(property);
622
623 // We don't want to make duplicates of existing tags
624 QSet<QString> existingTags;
625 foreach (const QContactTag& tag, contact->details<QContactTag>()) {
626 existingTags.insert(value: tag.tag());
627 }
628 foreach(const QString& value, values) {
629 if (!value.isEmpty() && !existingTags.contains(value)) {
630 QContactTag tag;
631 tag.setTag(value);
632 saveDetailWithContext(updatedDetails, detail: tag, contexts);
633 existingTags.insert(value);
634 }
635 }
636 return true;
637}
638
639/*!
640 * Creates a QContactOnlineAccount from \a property
641 */
642bool QVersitContactImporterPrivate::createOnlineAccount(
643 const QVersitProperty& property,
644 QContact* contact,
645 QList<QContactDetail>* updatedDetails)
646{
647 Q_UNUSED(contact)
648 QContactOnlineAccount onlineAccount;
649 QString value(property.value());
650 if (value.isEmpty())
651 return false;
652 onlineAccount.setAccountUri(property.value());
653 if (property.name() == QStringLiteral("X-SIP")) {
654 QStringList subTypes = extractSubTypes(property);
655 QList<int> subTypesInt;
656
657 foreach (const QString &stringValue, subTypes) {
658 QMultiHash<QString, QPair<QContactDetail::DetailType, int> >::const_iterator
659 it = mSubTypeMappings.constFind(akey: stringValue);
660 if (it != mSubTypeMappings.constEnd()) {
661 int mappedValue = it.value().second;
662 subTypesInt << mappedValue;
663 }
664 }
665 if (subTypes.isEmpty())
666 subTypesInt.append(t: QContactOnlineAccount::SubTypeSip);
667 onlineAccount.setSubTypes(subTypesInt);
668 } else if (property.name() == QStringLiteral("X-IMPP") ||
669 property.name() == QStringLiteral("IMPP")) {
670 QList<int> subTypeImppList;
671 subTypeImppList << QContactOnlineAccount::SubTypeImpp;
672 onlineAccount.setSubTypes(subTypeImppList);
673 } else if (property.name() == QStringLiteral("X-JABBER")) {
674 QList<int> subTypeImppList;
675 subTypeImppList << QContactOnlineAccount::SubTypeImpp;
676 onlineAccount.setSubTypes(subTypeImppList);
677 onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol,
678 value: QContactOnlineAccount::ProtocolJabber);
679 } else if (property.name() == QStringLiteral("X-AIM")) {
680 onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol,
681 value: QContactOnlineAccount::ProtocolAim);
682 } else if (property.name() == QStringLiteral("X-ICQ")) {
683 onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol,
684 value: QContactOnlineAccount::ProtocolIcq);
685 } else if (property.name() == QStringLiteral("X-MSN")) {
686 onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol,
687 value: QContactOnlineAccount::ProtocolMsn);
688 } else if (property.name() == QStringLiteral("X-QQ")) {
689 onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol,
690 value: QContactOnlineAccount::ProtocolQq);
691 } else if (property.name() == QStringLiteral("X-YAHOO")) {
692 onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol,
693 value: QContactOnlineAccount::ProtocolYahoo);
694 } else if (property.name() == QStringLiteral("X-SKYPE") ||
695 property.name() == QStringLiteral("X-SKYPE-USERNAME")) {
696 onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol,
697 value: QContactOnlineAccount::ProtocolSkype);
698 } else {
699 onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol,
700 value: QContactOnlineAccount::ProtocolUnknown);
701 }
702
703 saveDetailWithContext(updatedDetails, detail: onlineAccount, contexts: extractContexts(property));
704 return true;
705}
706
707/*!
708 * Creates a QContactRingtone from \a property
709 */
710bool QVersitContactImporterPrivate::createRingtone(
711 const QVersitProperty& property,
712 QContact* contact,
713 QList<QContactDetail>* updatedDetails)
714{
715 Q_UNUSED(contact)
716 QString location;
717 QByteArray data;
718 if (saveDataFromProperty(property, location: &location, data: &data) && !location.isEmpty()) {
719 QContactRingtone ringtone;
720 ringtone.setAudioRingtoneUrl(location);
721 saveDetailWithContext(updatedDetails, detail: ringtone, contexts: extractContexts(property));
722 return true;
723 }
724 return false;
725}
726
727/*!
728 * Creates a QContactAvatar from \a property
729 */
730bool QVersitContactImporterPrivate::createAvatar(
731 const QVersitProperty& property,
732 QContact* contact,
733 QList<QContactDetail>* updatedDetails)
734{
735 Q_UNUSED(contact)
736 QString location;
737 QByteArray data;
738 bool success = false;
739
740 if (saveDataFromProperty(property, location: &location, data: &data) && !location.isEmpty()) {
741 QContactAvatar avatar;
742 avatar.setImageUrl(location);
743 saveDetailWithContext(updatedDetails, detail: avatar, contexts: extractContexts(property));
744 success = true;
745 }
746 return success;
747}
748
749/*!
750 * Creates a QContactGeoLocation from \a property
751 */
752bool QVersitContactImporterPrivate::createGeoLocation(
753 const QVersitProperty& property,
754 QContact* contact,
755 QList<QContactDetail>* updatedDetails)
756{
757 Q_UNUSED(contact)
758 QContactGeoLocation geo;
759 QVariant variant = property.variantValue();
760 if (property.valueType() != QVersitProperty::CompoundType
761 || variant.type() != QVariant::StringList)
762 return false;
763 QStringList values = variant.toStringList();
764 bool ok1;
765 geo.setLatitude(takeFirst(list&: values).toDouble(ok: &ok1));
766 bool ok2;
767 geo.setLongitude(takeFirst(list&: values).toDouble(ok: &ok2));
768
769 if (ok1 && ok2) {
770 saveDetailWithContext(updatedDetails, detail: geo, contexts: extractContexts(property));
771 return true;
772 } else {
773 return false;
774 }
775}
776
777/*!
778 * Creates a QContactFamily from \a property
779 */
780bool QVersitContactImporterPrivate::createFamily(
781 const QVersitProperty& property,
782 QContact* contact,
783 QList<QContactDetail>* updatedDetails)
784{
785 QString val = property.value();
786 QContactFamily family = contact->detail<QContactFamily>();
787 if (property.name() == QStringLiteral("X-SPOUSE")) {
788 if (val.isEmpty())
789 return false;
790 family.setSpouse(val);
791 } else if (property.name() == QStringLiteral("X-CHILDREN")) {
792 QVariant variant = property.variantValue();
793 if (property.valueType() != QVersitProperty::ListType
794 || variant.type() != QVariant::StringList)
795 return false;
796 QStringList values = variant.toStringList();
797 if (values.isEmpty())
798 return false;
799 family.setChildren(values);
800 } else {
801 return false;
802 }
803
804 saveDetailWithContext(updatedDetails, detail: family, contexts: extractContexts(property));
805 return true;
806}
807
808/*!
809 * Creates a QContactFavorite from \a property
810 */
811bool QVersitContactImporterPrivate::createFavorite(
812 const QVersitProperty& property,
813 QContact* contact,
814 QList<QContactDetail>* updatedDetails)
815{
816 QContactDetail detail = contact->detail(type: QContactFavorite::Type);
817 if (!detail.isEmpty()) {
818 // If multiple favorite properties exist,
819 // discard all except the first occurrence
820 return false;
821 }
822
823 QContactFavorite favorite;
824 QVariant variant = property.variantValue();
825 if (property.valueType() != QVersitProperty::CompoundType
826 || variant.type() != QVariant::StringList)
827 return false;
828
829 QStringList values = variant.toStringList();
830
831 QString value(takeFirst(list&: values));
832 if (value.isEmpty())
833 return false;
834 if (value == QStringLiteral("true"))
835 favorite.setFavorite(true);
836 else if (value == QStringLiteral("false"))
837 favorite.setFavorite(false);
838 else
839 return false;
840
841 value = takeFirst(list&: values);
842 if (value.isEmpty())
843 return false;
844 bool ok = true;
845 int index = value.toInt(ok: &ok);
846 if (ok)
847 favorite.setIndex(index);
848 else
849 return false;
850
851 saveDetailWithContext(updatedDetails, detail: favorite, contexts: extractContexts(property));
852 return true;
853}
854
855/*!
856 * Creates a QContactGender from \a property
857 */
858bool QVersitContactImporterPrivate::createGender(
859 const QVersitProperty& property,
860 QContact* contact,
861 QList<QContactDetail>* updatedDetails)
862{
863 QContactGender gender;
864 QContactDetail detail = contact->detail(type: QContactGender::Type);
865 if (!detail.isEmpty()) {
866 // If multiple gender properties exist,
867 // discard all except the first occurrence
868 if (!detail.value(field: QContactGender::FieldGender).toBool())
869 return false;
870 else
871 gender = QContactGender(static_cast<QContactGender>(detail));
872 }
873 QString val = property.value().toUpper();
874 if (property.name() != QStringLiteral("X-GENDER") || val.isEmpty())
875 return false;
876 if (val == QStringLiteral("MALE")) {
877 gender.setGender(QContactGender::GenderMale);
878 } else if (val == QStringLiteral("FEMALE")) {
879 gender.setGender(QContactGender::GenderFemale);
880 } else if (val == QStringLiteral("UNSPECIFIED")) {
881 gender.setGender(QContactGender::GenderUnspecified);
882 } else {
883 return false;
884 }
885
886 saveDetailWithContext(updatedDetails, detail: gender, contexts: extractContexts(property));
887 return true;
888}
889
890/*!
891 * Creates a QContactExtendedDetail from \a property
892 */
893bool QVersitContactImporterPrivate::createExtendedDetail(
894 const QVersitProperty& property,
895 QContact* contact,
896 QList<QContactDetail>* updatedDetails)
897{
898 Q_UNUSED(contact)
899 QContactExtendedDetail extendedDetail;
900 const QVariant variant = property.variantValue();
901 if (property.valueType() != QVersitProperty::CompoundType
902 || variant.type() != QVariant::StringList)
903 return false;
904
905 QStringList values = variant.toStringList();
906 extendedDetail.setName(takeFirst(list&: values));
907 QVariant data;
908 if (VersitUtils::convertFromJson(json: takeFirst(list&: values), data: &data))
909 extendedDetail.setData(data);
910 else
911 return false;
912
913 saveDetailWithContext(updatedDetails, detail: extendedDetail, contexts: extractContexts(property));
914 return true;
915}
916
917/*!
918 * Creates a simple name-value contact detail.
919 */
920bool QVersitContactImporterPrivate::createNameValueDetail(
921 const QVersitProperty& property,
922 QContact* contact,
923 QList<QContactDetail>* updatedDetails)
924{
925 Q_UNUSED(contact)
926 QString value(property.value());
927 if (value.isEmpty())
928 return false;
929 QPair<QContactDetail::DetailType, int> nameAndValueType =
930 mDetailMappings.value(akey: property.name());
931 if (nameAndValueType.first == QContactDetail::TypeUndefined)
932 return false;
933
934 QContactDetail detail(nameAndValueType.first);
935 detail.setValue(field: nameAndValueType.second, value);
936
937 saveDetailWithContext(updatedDetails, detail, contexts: extractContexts(property));
938 return true;
939}
940
941/*!
942 * Extracts the list of contexts from \a types
943 */
944QList<int> QVersitContactImporterPrivate::extractContexts(
945 const QVersitProperty& property) const
946{
947 QStringList types = property.parameters().values(QStringLiteral("TYPE"));
948 QList<int> contexts;
949 foreach (const QString& type, types) {
950 QString value = type.toUpper();
951 if (mContextMappings.values().contains(t: value))
952 contexts << mContextMappings.key(avalue: value);
953 }
954 return contexts;
955}
956
957/*!
958 * Extracts the list of subtypes from \a property
959 */
960QStringList QVersitContactImporterPrivate::extractSubTypes(
961 const QVersitProperty& property) const
962{
963 QStringList types = property.parameters().values(QStringLiteral("TYPE"));
964 QStringList subTypes;
965 foreach (const QString& type, types) {
966 QString subType = type.toUpper();
967 if (subType.length() > 0)
968 subTypes += subType;
969 }
970 return subTypes;
971}
972
973/*!
974 * Takes the first value in \a list, or an empty QString is if the list is empty.
975 */
976QString QVersitContactImporterPrivate::takeFirst(QList<QString>& list) const
977{
978 return list.empty() ? QString() : list.takeFirst();
979}
980
981/*!
982 * Parses a date and time from text
983 */
984QDateTime QVersitContactImporterPrivate::parseDateTime(QString value, bool *justDate) const
985{
986 bool hasTime = false;
987 bool utc = value.endsWith(c: QLatin1Char('Z'), cs: Qt::CaseInsensitive);
988 if (utc)
989 value.chop(n: 1); // take away z from end;
990
991 QDateTime dateTime;
992 if (value.contains(c: QLatin1Char('-'))) {
993 dateTime = QDateTime::fromString(s: value,f: Qt::ISODate);
994 hasTime = dateTime.isValid() && value.contains(c: QLatin1Char('T'));
995 } else {
996 switch (value.length()) {
997 case 8:
998 dateTime = QDateTime::fromString(s: value, QStringLiteral("yyyyMMdd"));
999 break;
1000 case 15:
1001 dateTime = QDateTime::fromString(s: value, QStringLiteral("yyyyMMddThhmmss"));
1002 hasTime = true;
1003 break;
1004 // default: return invalid
1005 }
1006 }
1007
1008 if (utc)
1009 dateTime.setTimeSpec(Qt::UTC);
1010
1011 if (justDate)
1012 *justDate = !hasTime && !utc; // UTC implies a time of midnight
1013
1014 return dateTime;
1015}
1016
1017/*!
1018 * Extracts either a location (URI/filepath) from a \a property, or data (eg. if it was base64
1019 * encoded). If the property contains data, an attempt is made to save it and the location of the
1020 * saved resource is recovered to *\a location. The data is stored into *\a data.
1021 */
1022bool QVersitContactImporterPrivate::saveDataFromProperty(const QVersitProperty &property,
1023 QString *location,
1024 QByteArray *data) const
1025{
1026 bool found = false;
1027 const QString valueParam = property.parameters().value(QStringLiteral("VALUE")).toUpper();
1028 QVariant variant(property.variantValue());
1029 if (variant.type() == QVariant::String
1030 || valueParam == QStringLiteral("URL")
1031 || valueParam == QStringLiteral("URI")) {
1032 *location = property.value();
1033 found |= !location->isEmpty();
1034 } else if (variant.type() == QVariant::ByteArray) {
1035 *data = variant.toByteArray();
1036 if (!data->isEmpty()) {
1037 found = true;
1038 *location = saveContentToFile(property, data: *data);
1039 }
1040 }
1041 return found;
1042}
1043
1044/*!
1045 * Writes \a data to a file and returns the filename. \a property specifies the context in which
1046 * the data was found.
1047 */
1048QString QVersitContactImporterPrivate::saveContentToFile(
1049 const QVersitProperty& property, const QByteArray& data) const
1050{
1051 QString filename;
1052 bool ok = false;
1053 if (mResourceHandler)
1054 ok = mResourceHandler->saveResource(contents: data, property, location: &filename);
1055 return ok ? filename : QString();
1056}
1057
1058/*!
1059 * Adds \a detail to the \a updatedDetails list. Also sets the contexts to \a contexts if it is not
1060 * empty.
1061 */
1062void QVersitContactImporterPrivate::saveDetailWithContext(
1063 QList<QContactDetail>* updatedDetails,
1064 QContactDetail detail,
1065 const QList<int>& contexts)
1066{
1067 if (!contexts.isEmpty())
1068 detail.setContexts(contexts);
1069 updatedDetails->append(t: detail);
1070}
1071
1072QT_END_NAMESPACE_VERSIT
1073

source code of qtpim/src/versit/qversitcontactimporter_p.cpp