void tst_QVersitWriter::testByteArrayOutput() { const QByteArray vCard30( "BEGIN:VCARD\r\n" "VERSION:3.0\r\n" "FN:John\r\n" "END:VCARD\r\n"); delete mWriter; // we don't want the init()ed writer. QByteArray output; mWriter = new QVersitWriter(&output); QVERIFY(mWriter->device() == 0); QVersitDocument document(QVersitDocument::VCard30Type); document.setComponentType(QStringLiteral("VCARD")); QVersitProperty property; property.setName(QString(QStringLiteral("FN"))); property.setValue(QStringLiteral("John")); document.addProperty(property); QVERIFY2(mWriter->startWriting(document), QString::number(mWriter->error()).toLatin1().data()); QVERIFY2(mWriter->waitForFinished(), QString::number(mWriter->error()).toLatin1().data()); QCOMPARE(output, vCard30); }
void tst_QVersitDocument::testEquality() { QVersitDocument document1; QVersitDocument document2; QVERIFY(document1.isEmpty()); QVERIFY(document1 == document2); QVERIFY(!(document1 != document2)); QVersitProperty property; property.setName(QLatin1String("FN")); property.setValue(QLatin1String("John Citizen")); document2.addProperty(property); QVERIFY(!(document1 == document2)); QVERIFY(document1 != document2); QVERIFY(!document2.isEmpty()); document1.addProperty(property); QVERIFY(document1 == document2); QVERIFY(!(document1 != document2)); document2.clear(); QVERIFY(document2.isEmpty()); document1.clear(); QVERIFY(document1 == document2); QVERIFY(!(document1 != document2)); document2.setType(QVersitDocument::VCard21Type); QVERIFY(!(document1 == document2)); QVERIFY(document1 != document2); QVERIFY(!document2.isEmpty()); document2 = document1; QVERIFY(document1 == document2); }
void CntVersitPrefPlugin::propertyProcessed( const QVersitDocument& document, const QVersitProperty& property, const QContact& contact, bool* alreadyProcessed, QList<QContactDetail>* updatedDetails) { Q_UNUSED(document); Q_UNUSED(contact); if (*alreadyProcessed && !updatedDetails->isEmpty()) { QStringList typeParameters = property.parameters().values(QLatin1String("TYPE")); if (typeParameters.contains(QLatin1String("PREF"), Qt::CaseInsensitive)) { if ((mDetailMappings.value(property.name()) == QContactPhoneNumber::DefinitionName) || (mDetailMappings.value(property.name()) == QContactEmailAddress::DefinitionName) || (mDetailMappings.value(property.name()) == QContactOnlineAccount::DefinitionName) || (mDetailMappings.value(property.name()) == QContactUrl::DefinitionName)) { // This method is called before the corresponding detail gets imported to QContact. // setPreferredDetail() cannot be called here -> detail is stored and will be set // preferred after whole versit document is processed mPrefDetailList.append(updatedDetails->last()); } } } }
void MT_CntVersitMyCardPlugin::exportOwnContact() { //create contact QContact phonecontact; QContactName contactName; contactName.setFirstName("Jo"); contactName.setLastName("Black"); phonecontact.saveDetail(&contactName); QContactPhoneNumber number; number.setContexts("Home"); number.setSubTypes("Mobile"); number.setNumber("+02644424429"); phonecontact.saveDetail(&number); //set MyCard detail QContactDetail myCard(MYCARD_DEFINTION_NAME); phonecontact.saveDetail(&myCard); //export QList<QContact> list; list.append(phonecontact); QVersitContactExporter exporter; QVERIFY(exporter.exportContacts(list, QVersitDocument::VCard21Type)); QList<QVersitDocument> documents = exporter.documents(); //X-SELF property is exported if MyCard QVersitDocument document = documents.first(); QVersitProperty property; property.setName(QLatin1String("X-SELF")); property.setValue("0"); bool propertyFound = document.properties().contains(property); QVERIFY(propertyFound); }
void tst_QVersitDocument::testRemoveProperty() { // Remove an empty property. QCOMPARE(mVersitDocument->properties().count(), 0); QVersitProperty property; mVersitDocument->addProperty(property); mVersitDocument->removeProperty(property); QCOMPARE(mVersitDocument->properties().count(), 0); // A full property. property.setName(QLatin1String("TEL")); property.setGroups(QStringList(QLatin1String("HOME"))); QMultiHash<QString, QString> params; params.insert(QLatin1String("TYPE"), QLatin1String("HOME")); property.setParameters(params); property.setValue(QLatin1String("123")); mVersitDocument->addProperty(property); QCOMPARE(mVersitDocument->properties().count(), 1); QVersitProperty property2; property2.setName(QLatin1String("TEL")); // Remove with a partial property fails. mVersitDocument->removeProperty(property2); QCOMPARE(mVersitDocument->properties().count(), 1); property2.setGroups(QStringList(QLatin1String("HOME"))); property2.setParameters(params); property2.setValue(QLatin1String("123")); // Remove with a fully specified property succeeds. mVersitDocument->removeProperty(property2); QCOMPARE(mVersitDocument->properties().count(), 0); }
void SeasidePropertyHandler::propertyProcessed(const QVersitDocument &, const QVersitProperty &property, const QContact &, bool *alreadyProcessed, QList<QContactDetail> * updatedDetails) { if (property.name().toLower() == QLatin1String("photo")) { processPhoto(property, alreadyProcessed, updatedDetails); } else if (property.name().toLower() == QLatin1String("x-nemomobile-onlineaccount-demo")) { processOnlineAccount(property, alreadyProcessed, updatedDetails); } }
/*! * Encodes the groups and name in the \a property and writes it to the device */ void QVersitDocumentWriter::encodeGroupsAndName(const QVersitProperty& property) { QStringList groups = property.groups(); if (!groups.isEmpty()) { writeString(groups.join(QStringLiteral("."))); writeString(QStringLiteral(".")); } writeString(property.name()); }
void contactProcessed(const QContact& contact, QVersitDocument* document) { Q_UNUSED(contact) QVersitProperty property; property.setName("TEST-PROPERTY"); property.setValue("3"); document->addProperty(property); }
void tst_QVersitProperty::testEmbeddedDocument() { QVersitDocument document; QVersitProperty property; property.setName(QLatin1String("X-tension")); document.addProperty(property); mVersitProperty->setValue(QVariant::fromValue(document)); QList<QVersitProperty> embeddedDocumentProperties = mVersitProperty->value<QVersitDocument>().properties(); QCOMPARE(embeddedDocumentProperties.count(),1); QCOMPARE(embeddedDocumentProperties[0].name(),QLatin1String("X-TENSION")); }
/*! Returns the hash value for \a key. */ uint qHash(const QVersitProperty &key) { uint hash = QT_PREPEND_NAMESPACE(qHash)(key.name()) + QT_PREPEND_NAMESPACE(qHash)(key.value()); foreach (const QString& group, key.groups()) { hash += QT_PREPEND_NAMESPACE(qHash)(group); } QHash<QString,QString>::const_iterator it = key.parameters().constBegin(); QHash<QString,QString>::const_iterator end = key.parameters().constEnd(); while (it != end) { hash += QT_PREPEND_NAMESPACE(qHash)(it.key()) + QT_PREPEND_NAMESPACE(qHash)(it.value()); ++it; } return hash; }
foreach(const QVersitProperty& expectedProperty, expectedProperties) { QList<QVersitProperty> actualProperties = findPropertiesByName(subDocuments.first(), expectedProperty.name()); if (!actualProperties.contains(expectedProperty)) { qDebug() << "Actual:" << actualProperties; qDebug() << "Expected to find:" << expectedProperty; QVERIFY(false); } }
/*! * Encodes the \a property and writes it to the device. */ void QVCard30Writer::encodeVersitProperty(const QVersitProperty& property) { QVersitProperty modifiedProperty(property); QString name = mPropertyNameMappings.value(property.name(),property.name()); modifiedProperty.setName(name); encodeGroupsAndName(modifiedProperty); QVariant variant(modifiedProperty.variantValue()); if (variant.type() == QVariant::ByteArray) { modifiedProperty.insertParameter(QLatin1String("ENCODING"), QLatin1String("b")); } encodeParameters(modifiedProperty.parameters()); writeString(QLatin1String(":")); QString renderedValue; QByteArray renderedBytes; if (variant.canConvert<QVersitDocument>()) { QVersitDocument embeddedDocument = variant.value<QVersitDocument>(); QByteArray data; QBuffer buffer(&data); buffer.open(QIODevice::WriteOnly); QVCard30Writer subWriter(mType); subWriter.setCodec(mCodec); subWriter.setDevice(&buffer); subWriter.encodeVersitDocument(embeddedDocument); QString documentString(mCodec->toUnicode(data)); backSlashEscape(&documentString); renderedValue = documentString; } else if (variant.type() == QVariant::String) { renderedValue = variant.toString(); if (property.valueType() != QVersitProperty::PreformattedType) { backSlashEscape(&renderedValue); } } else if (variant.type() == QVariant::StringList) { // We need to backslash escape and concatenate the values in the list QStringList values = property.variantValue().toStringList(); QString separator; if (property.valueType() == QVersitProperty::CompoundType) { separator = QLatin1String(";"); } else { if (property.valueType() != QVersitProperty::ListType) { qWarning("Variant value is a QStringList but the property's value type is neither " "CompoundType or ListType"); } // Assume it's a ListType separator = QLatin1String(","); } bool first = true; foreach (QString value, values) { if (!(value.isEmpty() && property.valueType() == QVersitProperty::ListType)) { if (!first) { renderedValue += separator; } backSlashEscape(&value); renderedValue += value; first = false; } } } else if (variant.type() == QVariant::ByteArray) {
bool QVCardRestoreHandler::propertyProcessed( const QVersitProperty& property, QList<QContactDetail>* updatedDetails) { bool success = false; QString group; if (!property.groups().isEmpty()) group = property.groups().first(); if (property.name() == PropertyName) { if (property.groups().size() != 1) return false; QMultiHash<QString, QString> parameters = property.parameters(); QContactDetail::DetailType detailType = QContactDetail::DetailType(parameters.value(DetailTypeParameter).toUInt()); QString fieldName = parameters.value(FieldParameter); // Find a detail previously seen with the same definitionName, which was generated from // a property from the same group QContactDetail detail(detailType); foreach (const QContactDetail& previousDetail, mDetailGroupMap.detailsInGroup(group)) { if (previousDetail.type() == detailType) { detail = previousDetail; } } // If not found, it's a new empty detail with the definitionName set. detail.setValue(fieldName.toInt(), deserializeValue(property)); // Replace the equivalent detail in updatedDetails with the new one QMutableListIterator<QContactDetail> it(*updatedDetails); while (it.hasNext()) { if (it.next().key() == detail.key()) { it.remove(); break; } } updatedDetails->append(detail); success = true; } if (!group.isEmpty()) { // Keep track of which details were generated from which Versit groups foreach (const QContactDetail& detail, *updatedDetails) { mDetailGroupMap.insert(group, detail); } }
void tst_QVersitWriter::testWritingVersions() { mWriter->setDevice(mOutputDevice); mOutputDevice->open(QBuffer::ReadWrite); QVersitDocument document; QVersitProperty property; property.setName(QString(QString::fromLatin1("FN"))); property.setValue(QString::fromLatin1("John")); document.addProperty(property); QByteArray vCard30( "BEGIN:VCARD\r\n" "VERSION:3.0\r\n" "FN:John\r\n" "END:VCARD\r\n"); QByteArray vCard21( "BEGIN:VCARD\r\n" "VERSION:2.1\r\n" "FN:John\r\n" "END:VCARD\r\n"); // Given no type or componentType, it should be vCard 3.0 QVERIFY(mWriter->startWriting(document)); mWriter->waitForFinished(); QCOMPARE(mOutputDevice->buffer(), vCard30); // document type should override the guess document.setType(QVersitDocument::VCard21Type); mOutputDevice->buffer().clear(); mOutputDevice->seek(0); QVERIFY(mWriter->startWriting(document)); mWriter->waitForFinished(); QCOMPARE(mOutputDevice->buffer(), vCard21); // param to startWriting should override document type mOutputDevice->buffer().clear(); mOutputDevice->seek(0); QVERIFY(mWriter->startWriting(document, QVersitDocument::VCard30Type)); mWriter->waitForFinished(); QCOMPARE(mOutputDevice->buffer(), vCard30); }
void tst_QVersitContactPlugins::testImporterPlugins() { QVersitContactImporter importer("Test"); QVersitDocument document; document.setComponentType("VCARD"); QVersitProperty property; property.setName("FN"); property.setValue("Bob"); document.addProperty(property); QVERIFY(importer.importDocuments(QList<QVersitDocument>() << document)); QCOMPARE(importer.contacts().size(), 1); QList<QContactDetail> details(importer.contacts().first().details("TestDetail")); QCOMPARE(details.size(), 5); // The plugins have had their index set such that they should be executed in reverse order // Check that they are all loaded, and run in the correct order QCOMPARE(details.at(0).value<int>("Plugin"), 5); QCOMPARE(details.at(1).value<int>("Plugin"), 4); QCOMPARE(details.at(2).value<int>("Plugin"), 3); QCOMPARE(details.at(3).value<int>("Plugin"), 2); QCOMPARE(details.at(4).value<int>("Plugin"), 1); }
QDebug operator<<(QDebug dbg, const QVersitProperty& property) { QStringList groups = property.groups(); QString name = property.name(); QMultiHash<QString,QString> parameters = property.parameters(); dbg.nospace() << "QVersitProperty("; foreach (const QString& group, groups) { dbg.nospace() << group << '.'; } dbg.nospace() << name; QHash<QString,QString>::const_iterator it; for (it = parameters.constBegin(); it != parameters.constEnd(); ++it) { dbg.nospace() << ';' << it.key() << '=' << it.value(); } if (property.valueType() == QVersitProperty::VersitDocumentType) dbg.nospace() << ':' << property.value<QVersitDocument>(); else dbg.nospace() << ':' << property.variantValue(); dbg.nospace() << ')'; return dbg.maybeSpace(); }
void CntVersitFavoritePlugin::propertyProcessed( const QVersitDocument& document, const QVersitProperty& property, const QContact& contact, bool* alreadyProcessed, QList<QContactDetail>* updatedDetails) { Q_UNUSED(document); Q_UNUSED(contact); Q_UNUSED(alreadyProcessed); Q_UNUSED(updatedDetails); if (property.name().contains(QLatin1String("X-FAVORITE"), Qt::CaseInsensitive)) { // This method is called before the corresponding detail gets imported to QContact. // Detail is saved after whole versit document is processed. QContactFavorite favorite; favorite.setFavorite(true); favorite.setIndex(property.value().toInt()); mFavoriteDetailList.append(favorite); } }
/*! Called for each processed detail from QVersitContactExporter during contact export. */ void CntVersitMyCardPlugin::detailProcessed( const QContact& contact, const QContactDetail& detail, const QVersitDocument& document, QSet<QString>* processedFields, QList<QVersitProperty>* toBeRemoved, QList<QVersitProperty>* toBeAdded) { Q_UNUSED(contact); Q_UNUSED(processedFields); Q_UNUSED(document); Q_UNUSED(toBeRemoved); if (detail.definitionName() == MYCARD_DEFINITION_NAME) { QVersitProperty property; property.setName(QLatin1String("X-SELF")); property.setValue("0"); toBeAdded->append(property); } // QVersitContactExporter takes care of adding the toBeAdded properties // to the versit document. }
void tst_QVCard21Writer::testEncodeGroupsAndName() { QVersitProperty property; QByteArray result; QBuffer buffer(&result); mWriter->setDevice(&buffer); buffer.open(QIODevice::WriteOnly); // No groups property.setName(QString::fromLatin1("name")); QByteArray expected("NAME"); mWriter->encodeGroupsAndName(property); QCOMPARE(result, expected); // One group mWriter->writeCrlf(); // so it doesn't start folding buffer.close(); result.clear(); buffer.open(QIODevice::WriteOnly); property.setGroups(QStringList(QString::fromLatin1("group"))); expected = "group.NAME"; mWriter->encodeGroupsAndName(property); QCOMPARE(result, expected); // Two groups mWriter->writeCrlf(); // so it doesn't start folding buffer.close(); result.clear(); buffer.open(QIODevice::WriteOnly); QStringList groups(QString::fromLatin1("group1")); groups.append(QString::fromLatin1("group2")); property.setGroups(groups); expected = "group1.group2.NAME"; mWriter->encodeGroupsAndName(property); QCOMPARE(result, expected); }
void tst_QVersitDocument::testRemoveAllProperties() { QString name(QLatin1String("FN")); // Try to remove from an empty document QCOMPARE(0, mVersitDocument->properties().count()); mVersitDocument->removeProperties(name); QCOMPARE(0, mVersitDocument->properties().count()); // Try to remove from a non-empty document, name does not match QVersitProperty property; property.setName(QLatin1String("TEL")); mVersitDocument->addProperty(property); QCOMPARE(1, mVersitDocument->properties().count()); mVersitDocument->removeProperties(name); QCOMPARE(1, mVersitDocument->properties().count()); // Remove from a non-empty document, name matches mVersitDocument->removeProperties(QLatin1String("TEL")); QCOMPARE(0, mVersitDocument->properties().count()); // Remove from a document with two properties, first matches property.setName(name); mVersitDocument->addProperty(property); property.setName(QLatin1String("TEL")); mVersitDocument->addProperty(property); QCOMPARE(2, mVersitDocument->properties().count()); mVersitDocument->removeProperties(name); QCOMPARE(1, mVersitDocument->properties().count()); // Remove from a document with two properties, second matches property.setName(name); mVersitDocument->addProperty(property); QCOMPARE(2, mVersitDocument->properties().count()); mVersitDocument->removeProperties(name); QCOMPARE(1, mVersitDocument->properties().count()); }
void importExample() { //! [Import example] QVersitContactImporter importer; QVersitDocument document; QVersitProperty property; property.setName(QString::fromAscii("N")); property.setValue("Citizen;John;Q;;"); document.addProperty(property); property.setName(QString::fromAscii("X-UNKNOWN-PROPERTY")); property.setValue("some value"); document.addProperty(property); if (importer.importDocuments(QList<QVersitDocument>() << document)) { QList<QContact> contactList = importer.contacts(); // contactList.first() now contains the "N" property as a QContactName // propertyHandler.mUnknownProperties contains the list of unknown properties } //! [Import example] }
/*! Called for each processed versit property from QVersitContactImporter during contact import. */ void CntVersitMyCardPlugin::propertyProcessed( const QVersitDocument& document, const QVersitProperty& property, const QContact& contact, bool* alreadyProcessed, QList<QContactDetail>* updatedDetails) { Q_UNUSED(document); Q_UNUSED(contact); Q_UNUSED(updatedDetails); Q_UNUSED(contact); Q_UNUSED(alreadyProcessed); if (property.name() == QLatin1String("X-SELF")) { mIsMyCard = true; } }
/*! * Encodes the \a property and writes it to the device. */ void QVCard21Writer::encodeVersitProperty(const QVersitProperty& property) { encodeGroupsAndName(property); QMultiHash<QString,QString> parameters = property.parameters(); QVariant variant(property.variantValue()); QString renderedValue; QByteArray renderedBytes; /* Structured values need to have their components backslash-escaped (in vCard 2.1, semicolons must be escaped for compound values and commas must be escaped for list values). */ if (variant.type() == QVariant::StringList) { QStringList values = property.variantValue().toStringList(); QString separator; if (property.valueType() == QVersitProperty::CompoundType) { separator = QLatin1String(";"); } else { if (property.valueType() != QVersitProperty::ListType) { qWarning("Variant value is a QStringList but the property's value type is neither " "CompoundType or ListType"); } // Assume it's a ListType separator = QLatin1String(","); } QString replacement = QLatin1Char('\\') + separator; QRegExp separatorRegex = QRegExp(separator); // Check first if any of the values need to be UTF-8 encoded (if so, all of them must be // UTF-8 encoded) bool forceUtf8 = requiresUtf8(values); bool first = true; foreach (QString value, values) { if (!(value.isEmpty() && property.valueType() == QVersitProperty::ListType)) { encodeVersitValue(parameters, value, forceUtf8); if (!first) { renderedValue += separator; } renderedValue += value.replace(separatorRegex, replacement); first = false; } } } else if (variant.type() == QVariant::String) {
void tst_QVCard30Writer::testEncodeVersitProperty_data() { QTest::addColumn<QVersitProperty>("property"); QTest::addColumn<QByteArray>("expectedResult"); QVersitProperty property; QByteArray expectedResult; // No parameters expectedResult = "FN:John Citizen\r\n"; property.setName(QString::fromAscii("FN")); property.setValue(QString::fromAscii("John Citizen")); QTest::newRow("No parameters") << property << expectedResult; // With parameter(s) expectedResult = "TEL;TYPE=HOME:123\r\n"; property.setName(QString::fromAscii("TEL")); property.setValue(QString::fromAscii("123")); property.insertParameter(QString::fromAscii("TYPE"),QString::fromAscii("HOME")); QTest::newRow("With parameters, plain value") << property << expectedResult; // normal FN property is backslash escaped property.clear(); property.setName(QLatin1String("FN")); property.setValue(QLatin1String(";,:\\")); // semicolons, commas and backslashes are escaped (not colons, as per RFC2426) expectedResult = "FN:\\;\\,:\\\\\r\n"; QTest::newRow("FN property") << property << expectedResult; // Structured N property.setName(QLatin1String("N")); property.setValue(QStringList() << QLatin1String("La;st") // needs to be backslash escaped << QLatin1String("Fi,rst") << QLatin1String("Mi:ddle") << QLatin1String("Pr\\efix") // needs to be QP encoded << QLatin1String("Suffix")); property.setValueType(QVersitProperty::CompoundType); expectedResult = "N:La\\;st;Fi\\,rst;Mi:ddle;Pr\\\\efix;Suffix\r\n"; QTest::newRow("N property") << property << expectedResult; // Structured CATEGORIES property.setName(QLatin1String("CATEGORIES")); property.setValue(QStringList() << QLatin1String("re;d") << QLatin1String("gr,een") << QLatin1String("bl:ue") << QLatin1String("ye\\llow")); property.setValueType(QVersitProperty::ListType); expectedResult = "CATEGORIES:re\\;d,gr\\,een,bl:ue,ye\\\\llow\r\n"; QTest::newRow("CATEGORIES property") << property << expectedResult; // Convert X-NICKNAME to NICKNAME expectedResult = "NICKNAME:Jack\r\n"; property.setParameters(QMultiHash<QString,QString>()); property.setName(QString::fromAscii("X-NICKNAME")); property.setValue(QString::fromAscii("Jack")); QTest::newRow("NICKNAME property") << property << expectedResult; // Convert X-IMPP to IMPP; expectedResult = "IMPP:msn:msn-address\r\n"; property.setParameters(QMultiHash<QString,QString>()); property.setName(QString::fromAscii("X-IMPP")); property.setValue(QString::fromAscii("msn:msn-address")); QTest::newRow("IMPP property") << property << expectedResult; // AGENT property expectedResult = "AGENT:BEGIN:VCARD\\nVERSION:3.0\\nFN:Secret Agent\\nEND:VCARD\\n\r\n"; property.setName(QString::fromAscii("AGENT")); property.setValue(QString()); QVersitDocument document(QVersitDocument::VCard30Type); document.setComponentType(QLatin1String("VCARD")); QVersitProperty embeddedProperty; embeddedProperty.setName(QString(QString::fromAscii("FN"))); embeddedProperty.setValue(QString::fromAscii("Secret Agent")); document.addProperty(embeddedProperty); property.setValue(QVariant::fromValue(document)); QTest::newRow("AGENT property") << property << expectedResult; // Value is base64 encoded. QByteArray value("value"); expectedResult = "Springfield.HOUSE.PHOTO;ENCODING=b:" + value.toBase64() + "\r\n"; QStringList groups(QString::fromAscii("Springfield")); groups.append(QString::fromAscii("HOUSE")); property.setGroups(groups); property.setParameters(QMultiHash<QString,QString>()); property.setName(QString::fromAscii("PHOTO")); property.setValue(value); QTest::newRow("base64 encoded") << property << expectedResult; // Characters other than ASCII: expectedResult = "ORG:" + KATAKANA_NOKIA.toUtf8() + "\r\n"; property = QVersitProperty(); property.setName(QLatin1String("ORG")); property.setValue(KATAKANA_NOKIA); QTest::newRow("non-ASCII") << property << expectedResult; // No CHARSET and QUOTED-PRINTABLE parameters expectedResult = "EMAIL:john@" + KATAKANA_NOKIA.toUtf8() + ".com\r\n"; property = QVersitProperty(); property.setName(QLatin1String("EMAIL")); property.setValue(QString::fromAscii("john@%1.com").arg(KATAKANA_NOKIA)); QTest::newRow("special chars") << property << expectedResult; }
void tst_QVersitWriter::testWritingDocument_data() { QTest::addColumn<QVersitDocument>("document"); QTest::addColumn<QByteArray>("expected"); QVersitDocument document(QVersitDocument::VCard21Type); document.setComponentType(QStringLiteral("VCARD")); QVersitProperty property; property.setName(QStringLiteral("FN")); property.setValue(QStringLiteral("Bob")); document.addProperty(property); QTest::newRow("basic vCard 2.1") << document << QByteArray( "BEGIN:VCARD\r\n" "VERSION:2.1\r\n" "FN:Bob\r\n" "END:VCARD\r\n" ); document.setComponentType(QStringLiteral("VCARD")); document.setType(QVersitDocument::VCard30Type); QTest::newRow("basic vCard 3.0") << document << QByteArray( "BEGIN:VCARD\r\n" "VERSION:3.0\r\n" "FN:Bob\r\n" "END:VCARD\r\n" ); document.setComponentType(QStringLiteral("VCARD")); document.setType(QVersitDocument::VCard40Type); QTest::newRow("basic vCard 4.0") << document << QByteArray( "BEGIN:VCARD\r\n" "VERSION:4.0\r\n" "FN:Bob\r\n" "END:VCARD\r\n" ); { QVersitDocument document(QVersitDocument::ICalendar20Type); document.setComponentType(QStringLiteral("VCALENDAR")); QVersitDocument subdocument(QVersitDocument::ICalendar20Type); subdocument.setComponentType(QStringLiteral("VEVENT")); property.setValueType(QVersitProperty::PreformattedType); property.setName(QStringLiteral("RRULE")); property.setValue(QStringLiteral("FREQ=MONTHLY;BYMONTHDAY=1,3")); subdocument.addProperty(property); document.addSubDocument(subdocument); QTest::newRow("basic iCalendar 2.0") << document << QByteArray( "BEGIN:VCALENDAR\r\n" "VERSION:2.0\r\n" "BEGIN:VEVENT\r\n" "RRULE:FREQ=MONTHLY;BYMONTHDAY=1,3\r\n" "END:VEVENT\r\n" "END:VCALENDAR\r\n"); } { QVersitDocument document(QVersitDocument::ICalendar20Type); document.setComponentType(QStringLiteral("VCALENDAR")); QVersitProperty property; property.setName(QStringLiteral("PRODID")); property.setValue(QStringLiteral("-//hacksw/handcal//NONSGML v1.0//EN")); document.addProperty(property); QVersitDocument nested(QVersitDocument::ICalendar20Type); nested.setComponentType(QStringLiteral("VEVENT")); property.setName(QStringLiteral("DTSTART")); property.setValue(QStringLiteral("19970714T170000Z")); nested.addProperty(property); property.setName(QStringLiteral("DTEND")); property.setValue(QStringLiteral("19970715T035959Z")); nested.addProperty(property); property.setName(QStringLiteral("SUMMARY")); property.setValue(QStringLiteral("Bastille Day Party")); nested.addProperty(property); document.addSubDocument(nested); QTest::newRow("iCalendar 2.0 from spec") << document << QByteArray( "BEGIN:VCALENDAR\r\n" "VERSION:2.0\r\n" "PRODID:-//hacksw/handcal//NONSGML v1.0//EN\r\n" "BEGIN:VEVENT\r\n" "DTSTART:19970714T170000Z\r\n" "DTEND:19970715T035959Z\r\n" "SUMMARY:Bastille Day Party\r\n" "END:VEVENT\r\n" "END:VCALENDAR\r\n"); } { QString longString(QLatin1String( "4567890123456789012345678901234567890123456789012345678901234567890123456" "234567890123456789012345678901234567890123456789012345678901234567890123456" "234567890123456789012")); QVersitDocument document(QVersitDocument::VCard21Type); document.setComponentType(QStringLiteral("VCARD")); QVersitProperty property; property.setName(QStringLiteral("FN")); property.setValue(longString); document.addProperty(property); QByteArray expected21( "BEGIN:VCARD\r\n" "VERSION:2.1\r\n" "FN:4567890123456789012345678901234567890123456789012345678901234567890123456\r\n" " 234567890123456789012345678901234567890123456789012345678901234567890123456\r\n" " 234567890123456789012\r\n" "END:VCARD\r\n"); QTest::newRow("folding 2.1") << document << expected21; document.setType(QVersitDocument::VCard30Type); QByteArray expected30( "BEGIN:VCARD\r\n" "VERSION:3.0\r\n" "FN:4567890123456789012345678901234567890123456789012345678901234567890123456\r\n" " 234567890123456789012345678901234567890123456789012345678901234567890123456\r\n" " 234567890123456789012\r\n" "END:VCARD\r\n"); QTest::newRow("folding 3.0") << document << expected30; document.setType(QVersitDocument::VCard21Type); property.setValue(longString.toLatin1()); property.setValueType(QVersitProperty::BinaryType); document.removeProperties("FN"); document.addProperty(property); QByteArray expected21b( "BEGIN:VCARD\r\n" "VERSION:2.1\r\n" "FN;ENCODING=BASE64:\r\n" " NDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODk\r\n" " wMTIzNDU2Nzg5MDEyMzQ1NjIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MD\r\n" " EyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1NjIzNDU2Nzg5MDEyMzQ1Njc4OTAxM\r\n" " g==\r\n" "\r\n" "END:VCARD\r\n"); QTest::newRow("folding 2.1") << document << expected21b; document.setType(QVersitDocument::VCard30Type); QByteArray expected30b( "BEGIN:VCARD\r\n" "VERSION:3.0\r\n" "FN;ENCODING=b:NDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OT\r\n" " AxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1NjIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwM\r\n" " TIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1NjIzNDU2Nzg5MDEy\r\n" " MzQ1Njc4OTAxMg==\r\n" "END:VCARD\r\n"); QTest::newRow("folding 3.0") << document << expected30b; } }
void tst_QVCard21Writer::testEncodeVersitProperty_data() { QTest::addColumn<QVersitProperty>("property"); QTest::addColumn<QByteArray>("expectedResult"); QTest::addColumn<QByteArray>("codec"); QVersitProperty property; QByteArray expectedResult; QByteArray codec("ISO-8859_1"); // normal case property.setName(QString::fromLatin1("FN")); property.setValue(QString::fromLatin1("John Citizen")); property.setValueType(QVersitProperty::PlainType); expectedResult = "FN:John Citizen\r\n"; QTest::newRow("No parameters") << property << expectedResult << codec; // Structured N - escaping should happen for semicolons, not for commas property.setName(QStringLiteral("N")); property.setValue(QStringList() << QStringLiteral("La;st") // needs to be backslash escaped << QStringLiteral("Fi,rst") << QStringLiteral("Mi:ddle") << QStringLiteral("Pr\\efix") // needs to be QP encoded << QStringLiteral("Suffix")); property.setValueType(QVersitProperty::CompoundType); expectedResult = "N;ENCODING=QUOTED-PRINTABLE:La\\;st;Fi,rst;Mi:ddle;Pr=5Cefix;Suffix\r\n"; QTest::newRow("N property") << property << expectedResult << codec; // Structured N - there was a bug where if two fields had to be escaped, // two ENCODING parameters were added property.setName(QStringLiteral("N")); property.setValue(QStringList() << QStringLiteral("La\\st") << QStringLiteral("Fi\\rst") << QString() << QString() << QString()); property.setValueType(QVersitProperty::CompoundType); expectedResult = "N;ENCODING=QUOTED-PRINTABLE:La=5Cst;Fi=5Crst;;;\r\n"; QTest::newRow("N property, double-encoded") << property << expectedResult << codec; // Structured N - one field needs to be encoded in UTF-8 while the other doesn't // correct behaviour is to encode the whole thing in UTF-8 property.setName(QStringLiteral("N")); property.setValue(QStringList() << QString::fromUtf8("\xE2\x82\xAC") // euro sign << QString::fromLatin1("\xA3") // pound sign (upper Latin-1) << QString() << QString() << QString()); property.setValueType(QVersitProperty::CompoundType); expectedResult = "N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E2=82=AC;=C2=A3;;;\r\n"; QTest::newRow("N property, double-encoded") << property << expectedResult << codec; // Structured CATEGORIES - escaping should happen for commas, not semicolons property.setName(QStringLiteral("CATEGORIES")); property.setValue(QStringList() << QStringLiteral("re;d") << QStringLiteral("gr,een") << QStringLiteral("bl:ue")); property.setValueType(QVersitProperty::ListType); expectedResult = "CATEGORIES:re;d,gr\\,een,bl:ue\r\n"; QTest::newRow("CATEGORIES property") << property << expectedResult << codec; // With parameter(s). No special characters in the value. // -> No need to Quoted-Printable encode the value. expectedResult = "TEL;HOME:123\r\n"; property.setName(QString::fromLatin1("TEL")); property.setValue(QString::fromLatin1("123")); property.insertParameter(QString::fromLatin1("TYPE"),QString::fromLatin1("HOME")); QTest::newRow("With parameters, plain value") << property << expectedResult << codec; expectedResult = "EMAIL;HOME;ENCODING=QUOTED-PRINTABLE:john.citizen=40example.com\r\n"; property.setName(QString::fromLatin1("EMAIL")); property.setValue(QString::fromLatin1("*****@*****.**")); QTest::newRow("With parameters, special value") << property << expectedResult << codec; // AGENT property with parameter expectedResult = "AGENT;X-PARAMETER=VALUE:\r\n\ BEGIN:VCARD\r\n\ VERSION:2.1\r\n\ FN:Secret Agent\r\n\ END:VCARD\r\n\ \r\n"; property.setParameters(QMultiHash<QString,QString>()); property.setName(QString::fromLatin1("AGENT")); property.setValue(QString()); property.insertParameter(QString::fromLatin1("X-PARAMETER"),QString::fromLatin1("VALUE")); QVersitDocument document(QVersitDocument::VCard21Type); document.setComponentType(QStringLiteral("VCARD")); QVersitProperty embeddedProperty; embeddedProperty.setName(QString(QString::fromLatin1("FN"))); embeddedProperty.setValue(QString::fromLatin1("Secret Agent")); document.addProperty(embeddedProperty); property.setValue(QVariant::fromValue(document)); QTest::newRow("AGENT property") << property << expectedResult << codec; // Value is base64 encoded. // Check that the extra folding and the line break are added QByteArray value("value"); expectedResult = "Springfield.HOUSE.PHOTO;ENCODING=BASE64:\r\n " + value.toBase64() + "\r\n\r\n"; QStringList groups(QString::fromLatin1("Springfield")); groups.append(QString::fromLatin1("HOUSE")); property.setGroups(groups); property.setParameters(QMultiHash<QString,QString>()); property.setName(QString::fromLatin1("PHOTO")); property.setValue(value); QTest::newRow("base64 encoded") << property << expectedResult << codec; // Characters other than ASCII: // Note: KATAKANA_NOKIA is defined as: QString::fromUtf8("\xe3\x83\x8e\xe3\x82\xad\xe3\x82\xa2") // The expected behaviour is to convert to UTF8, then encode with quoted-printable. // Because the result overflows one line, it should be split onto two lines using a // quoted-printable soft line break (EQUALS-CR-LF). (Note: Versit soft line breaks // (CR-LF-SPACE) are not supported by the native Symbian vCard importers). expectedResult = "ORG;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E3=83=8E=E3=82=AD=E3=82=A2=E3=\r\n" "=83=8E=E3=82=AD=E3=82=A2\r\n"; property = QVersitProperty(); property.setName(QStringLiteral("ORG")); property.setValue(KATAKANA_NOKIA + KATAKANA_NOKIA); QTest::newRow("non-ASCII 1") << property << expectedResult << codec; expectedResult = "ORG;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:a=E3=83=8E=E3=82=AD=E3=82=A2=E3=\r\n" "=83=8E=E3=82=AD=E3=82=A2\r\n"; property = QVersitProperty(); property.setName(QStringLiteral("ORG")); property.setValue("a" + KATAKANA_NOKIA + KATAKANA_NOKIA); QTest::newRow("non-ASCII 2") << property << expectedResult << codec; expectedResult = "ORG;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:aa=E3=83=8E=E3=82=AD=E3=82=A2=\r\n" "=E3=83=8E=E3=82=AD=E3=82=A2\r\n"; property = QVersitProperty(); property.setName(QStringLiteral("ORG")); property.setValue("aa" + KATAKANA_NOKIA + KATAKANA_NOKIA); QTest::newRow("non-ASCII 3") << property << expectedResult << codec; // In Shift-JIS codec. QTextCodec* jisCodec = QTextCodec::codecForName("Shift-JIS"); expectedResult = jisCodec->fromUnicode( QStringLiteral("ORG:") + KATAKANA_NOKIA + QStringLiteral("\r\n")); property = QVersitProperty(); property.setName(QStringLiteral("ORG")); property.setValue(KATAKANA_NOKIA); QTest::newRow("JIS codec") << property << expectedResult << QByteArray("Shift-JIS"); }
void tst_QVersitOrganizerExporter::testExport_data() { QTest::addColumn<QList<QOrganizerItem> >("items"); QTest::addColumn<QVersitDocument>("expectedDocument"); { QVersitDocument document(QVersitDocument::ICalendar20Type); document.setComponentType(QLatin1String("VCALENDAR")); QVersitDocument nested(QVersitDocument::ICalendar20Type); nested.setComponentType(QLatin1String("VEVENT")); QVersitProperty property; property.setName(QLatin1String("SUMMARY")); property.setValue(QLatin1String("Bastille Day Party")); nested.addProperty(property); property.setName(QLatin1String("DTSTART")); property.setValue(QLatin1String("19970714T170000Z")); nested.addProperty(property); property.setName(QLatin1String("DTEND")); property.setValue(QLatin1String("19970715T035959Z")); nested.addProperty(property); document.addSubDocument(nested); nested.clear(); nested.setType(QVersitDocument::ICalendar20Type); nested.setComponentType(QLatin1String("VTODO")); property.setName(QLatin1String("SUMMARY")); property.setValue(QLatin1String("Take out the garbage")); nested.addProperty(property); property.setName(QLatin1String("DTSTART")); property.setValue(QLatin1String("20100609T080000")); nested.addProperty(property); property.setName(QLatin1String("DUE")); property.setValue(QLatin1String("20100610T080000")); nested.addProperty(property); document.addSubDocument(nested); nested.clear(); nested.setType(QVersitDocument::ICalendar20Type); nested.setComponentType(QLatin1String("VJOURNAL")); property.setName(QLatin1String("SUMMARY")); property.setValue(QLatin1String("Trip to Thailand")); nested.addProperty(property); property.setName(QLatin1String("DTSTART")); property.setValue(QLatin1String("20100615T112300")); nested.addProperty(property); document.addSubDocument(nested); QOrganizerEvent event; event.setDisplayLabel(QLatin1String("Bastille Day Party")); event.setStartDateTime(QDateTime(QDate(1997, 7, 14), QTime(17, 0, 0), Qt::UTC)); event.setEndDateTime(QDateTime(QDate(1997, 7, 15), QTime(3, 59, 59), Qt::UTC)); QOrganizerTodo todo; todo.setDisplayLabel(QLatin1String("Take out the garbage")); todo.setStartDateTime(QDateTime(QDate(2010, 6, 9), QTime(8, 0, 0))); todo.setDueDateTime(QDateTime(QDate(2010, 6, 10), QTime(8, 0, 0))); QOrganizerJournal journal; journal.setDisplayLabel(QLatin1String("Trip to Thailand")); journal.setDateTime(QDateTime(QDate(2010, 6, 15), QTime(11, 23, 0))); QList<QOrganizerItem> items; items << static_cast<QOrganizerItem>(event); items << static_cast<QOrganizerItem>(todo); items << static_cast<QOrganizerItem>(journal); QTest::newRow("sample event, todo and journal") << items << document; } { QVersitDocument document(QVersitDocument::ICalendar20Type); document.setComponentType(QLatin1String("VCALENDAR")); QVersitDocument nested(QVersitDocument::ICalendar20Type); nested.setComponentType(QLatin1String("VEVENT")); QVersitProperty property; property.setName(QLatin1String("UID")); property.setValue(QLatin1String("1234")); nested.addProperty(property); property.setName(QLatin1String("RECURRENCE-ID")); property.setValue(QLatin1String("20100608")); nested.addProperty(property); document.addSubDocument(nested); // An event occurrence with OriginalDate and Guid set QOrganizerEventOccurrence event; event.setGuid(QLatin1String("1234")); event.setOriginalDate(QDate(2010, 6, 8)); QList<QOrganizerItem> items; items << static_cast<QOrganizerItem>(event); QTest::newRow("event occurrence exception") << items << document; } }
// ... then, do the rest of the properties. foreach (const QVersitProperty& property, properties) { QStringList typeParameters = property.parameters().values(QStringLiteral("TYPE")); if (!typeParameters.contains(QStringLiteral("PREF"), Qt::CaseInsensitive)) importProperty(document, property, contactIndex, contact); }
void SeasidePhotoHandler::propertyProcessed(const QVersitDocument &, const QVersitProperty &property, const QContact &, bool *alreadyProcessed, QList<QContactDetail> * updatedDetails) { // if the property is a PHOTO property, store the data to disk // and then create an avatar detail which points to it. if (property.name().toLower() != QLatin1String("photo")) return; #ifndef QT_VERSION_5 // The Qt4 / QtMobility version has QContactThumbnail support. // We need to remove any such thumbnail detail from the output, // as some backends (such as qtcontacts-sqlite) do not support // that detail type. for (int i = 0; i < updatedDetails->size(); ++i) { if (updatedDetails->at(i).definitionName() == QContactThumbnail::DefinitionName) { updatedDetails->removeAt(i); --i; } } #endif // The data might be either a URL, a file path, or encoded image data // It's hard to tell what the content is, because versit removes the encoding // information in the process of decoding the data... // Try to interpret the data as a URL QString path(property.variantValue().toString()); QUrl url(path); if (url.isValid()) { // Treat remote URL as a true URL, and reference it in the avatar if (!url.scheme().isEmpty() && !url.isLocalFile()) { QContactAvatar newAvatar; newAvatar.setImageUrl(url); updatedDetails->append(newAvatar); // we have successfully processed this PHOTO property. *alreadyProcessed = true; return; } } if (!url.isValid()) { // See if we can resolve the data as a local file path url = QUrl::fromLocalFile(path); } QByteArray photoData; if (url.isValid()) { // Try to read the data from the referenced file const QString filePath(url.path()); if (QFile::exists(filePath)) { QFile file(filePath); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "Unable to process photo data as file:" << path; return; } else { photoData = file.readAll(); } } } if (photoData.isEmpty()) { // Try to interpret the encoded property data as the image photoData = property.variantValue().toByteArray(); if (photoData.isEmpty()) { qWarning() << "Failed to extract avatar data from vCard PHOTO property"; return; } } QImage img; bool loaded = img.loadFromData(photoData); if (!loaded) { qWarning() << "Failed to load avatar image from vCard PHOTO data"; return; } // We will save the avatar image to disk in the system's data location // Since we're importing user data, it should not require privileged access const QString subdirectory(QString::fromLatin1(".local/share/system/Contacts/avatars")); const QString photoDirPath(QDir::home().filePath(subdirectory)); // create the photo file dir if it doesn't exist. QDir photoDir; if (!photoDir.mkpath(photoDirPath)) { qWarning() << "Failed to create avatar image directory when loading avatar image from vCard PHOTO data"; return; } // construct the filename of the new avatar image. QString photoFilePath = QString::fromLatin1(QCryptographicHash::hash(photoData, QCryptographicHash::Md5).toHex()); photoFilePath = photoDirPath + QDir::separator() + photoFilePath + QString::fromLatin1(".jpg"); // save the file to disk bool saved = img.save(photoFilePath); if (!saved) { qWarning() << "Failed to save avatar image from vCard PHOTO data to" << photoFilePath; return; } qWarning() << "Successfully saved avatar image from vCard PHOTO data to" << photoFilePath; // save the avatar detail - TODO: mark the avatar as "owned by the contact" (remove on delete) QContactAvatar newAvatar; newAvatar.setImageUrl(QUrl::fromLocalFile(photoFilePath)); updatedDetails->append(newAvatar); // we have successfully processed this PHOTO property. *alreadyProcessed = true; }