void ICalFormatTest::testCharsets() { ICalFormat format; const QDate currentDate = QDate::currentDate(); Event::Ptr event = Event::Ptr( new Event() ); event->setUid( "12345" ); event->setDtStart( KDateTime( currentDate ) ); event->setDtEnd( KDateTime( currentDate.addDays( 1 ) ) ); // ü const QChar latin1_umlaut[] = { 0xFC, '\0' }; event->setSummary( QString( latin1_umlaut ) ); // Test if toString( Incidence ) didn't mess charsets const QString serialized = format.toString( event.staticCast<Incidence>() ); const QChar utf_umlaut[] = { 0xC3, 0XBC, '\0' }; QVERIFY( serialized.toUtf8().contains( QString( utf_umlaut ).toLatin1().constData() ) ); QVERIFY( !serialized.toUtf8().contains( QString( latin1_umlaut ).toLatin1().constData() ) ); QVERIFY( serialized.toLatin1().contains( QString( latin1_umlaut ).toLatin1().constData() ) ); QVERIFY( !serialized.toLatin1().contains( QString( utf_umlaut ).toLatin1().constData() ) ); // test fromString( QString ) const QString serializedCalendar = "BEGIN:VCALENDAR\nPRODID:-//K Desktop Environment//NONSGML libkcal 3.2//EN\nVERSION:2.0\n" + serialized + "\nEND:VCALENDAR"; Incidence::Ptr event2 = format.fromString( serializedCalendar ); QVERIFY( event->summary() == event2->summary() ); QVERIFY( event2->summary().toUtf8() == QByteArray( QString( utf_umlaut ).toLatin1().constData() ) ); // test save() MemoryCalendar::Ptr calendar( new MemoryCalendar( "UTC" ) ); calendar->addIncidence( event ); QVERIFY( format.save( calendar, "hommer.ics" ) ); // Make sure hommer.ics is in UTF-8 QFile file( "hommer.ics" ); QVERIFY( file.open( QIODevice::ReadOnly | QIODevice::Text ) ); const QByteArray bytesFromFile = file.readAll(); QVERIFY( bytesFromFile.contains( QString( utf_umlaut ).toLatin1().constData() ) ); QVERIFY( !bytesFromFile.contains( QString( latin1_umlaut ).toLatin1().constData() ) ); file.close(); // Test load: MemoryCalendar::Ptr calendar2( new MemoryCalendar( "UTC" ) ); QVERIFY( format.load( calendar2, "hommer.ics" ) ); QVERIFY( calendar2->incidences().count() == 1 ); // kDebug() << format.toString( event.staticCast<Incidence>() ); // kDebug() << format.toString( calendar2->incidences().first() ); Event::Ptr loadedEvent = calendar2->incidences().first().staticCast<Event>(); QVERIFY( loadedEvent->summary().toUtf8() == QByteArray( QString( utf_umlaut ).toLatin1().constData() ) ); QVERIFY( *loadedEvent == *event ); // Test fromRawString() MemoryCalendar::Ptr calendar3( new MemoryCalendar( "UTC" ) ); format.fromRawString( calendar3, bytesFromFile ); QVERIFY( calendar3->incidences().count() == 1 ); QVERIFY( *calendar3->incidences().first() == *event ); unlink( "hommer.ics" ); }
QString KTnef::msTNEFToVPart( const QByteArray &tnef ) { bool bOk = false; KTNEFParser parser; QByteArray b( tnef ); QBuffer buf( &b ); MemoryCalendar::Ptr cal( new MemoryCalendar( KDateTime::UTC ) ); KABC::Addressee addressee; ICalFormat calFormat; Event::Ptr event( new Event() ); if ( parser.openDevice( &buf ) ) { KTNEFMessage *tnefMsg = parser.message(); //QMap<int,KTNEFProperty*> props = parser.message()->properties(); // Everything depends from property PR_MESSAGE_CLASS // (this is added by KTNEFParser): QString msgClass = tnefMsg->findProp( 0x001A, QString(), true ).toUpper(); if ( !msgClass.isEmpty() ) { // Match the old class names that might be used by Outlook for // compatibility with Microsoft Mail for Windows for Workgroups 3.1. bool bCompatClassAppointment = false; bool bCompatMethodRequest = false; bool bCompatMethodCancled = false; bool bCompatMethodAccepted = false; bool bCompatMethodAcceptedCond = false; bool bCompatMethodDeclined = false; if ( msgClass.startsWith( QLatin1String( "IPM.MICROSOFT SCHEDULE." ) ) ) { bCompatClassAppointment = true; if ( msgClass.endsWith( QLatin1String( ".MTGREQ" ) ) ) { bCompatMethodRequest = true; } if ( msgClass.endsWith( QLatin1String( ".MTGCNCL" ) ) ) { bCompatMethodCancled = true; } if ( msgClass.endsWith( QLatin1String( ".MTGRESPP" ) ) ) { bCompatMethodAccepted = true; } if ( msgClass.endsWith( QLatin1String( ".MTGRESPA" ) ) ) { bCompatMethodAcceptedCond = true; } if ( msgClass.endsWith( QLatin1String( ".MTGRESPN" ) ) ) { bCompatMethodDeclined = true; } } bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" ); if ( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) { // Compose a vCal bool bIsReply = false; QString prodID = "-//Microsoft Corporation//Outlook "; prodID += tnefMsg->findNamedProp( "0x8554", "9.0" ); prodID += "MIMEDIR/EN\n"; prodID += "VERSION:2.0\n"; calFormat.setApplication( "Outlook", prodID ); iTIPMethod method; if ( bCompatMethodRequest ) { method = iTIPRequest; } else if ( bCompatMethodCancled ) { method = iTIPCancel; } else if ( bCompatMethodAccepted || bCompatMethodAcceptedCond || bCompatMethodDeclined ) { method = iTIPReply; bIsReply = true; } else { // pending(khz): verify whether "0x0c17" is the right tag ??? // // at the moment we think there are REQUESTS and UPDATES // // but WHAT ABOUT REPLIES ??? // // if ( tnefMsg->findProp(0x0c17) == "1" ) { bIsReply = true; } method = iTIPRequest; } /// ### FIXME Need to get this attribute written ScheduleMessage schedMsg( event, method, ScheduleMessage::Unknown ); QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) ); if ( !sSenderSearchKeyEmail.isEmpty() ) { int colon = sSenderSearchKeyEmail.indexOf( ':' ); // May be e.g. "SMTP:[email protected]" if ( sSenderSearchKeyEmail.indexOf( ':' ) == -1 ) { sSenderSearchKeyEmail.remove( 0, colon+1 ); } } QString s( tnefMsg->findProp( 0x8189 ) ); const QStringList attendees = s.split( ';' ); if ( attendees.count() ) { for ( QStringList::const_iterator it = attendees.begin(); it != attendees.end(); ++it ) { // Skip all entries that have no '@' since these are // no mail addresses if ( (*it).indexOf( '@' ) == -1 ) { s = (*it).trimmed(); Attendee::Ptr attendee( new Attendee( s, s, true ) ); if ( bIsReply ) { if ( bCompatMethodAccepted ) { attendee->setStatus( Attendee::Accepted ); } if ( bCompatMethodDeclined ) { attendee->setStatus( Attendee::Declined ); } if ( bCompatMethodAcceptedCond ) { attendee->setStatus( Attendee::Tentative ); } } else { attendee->setStatus( Attendee::NeedsAction ); attendee->setRole( Attendee::ReqParticipant ); } event->addAttendee( attendee ); } } } else { // Oops, no attendees? // This must be old style, let us use the PR_SENDER_SEARCH_KEY. s = sSenderSearchKeyEmail; if ( !s.isEmpty() ) { Attendee::Ptr attendee( new Attendee( QString(), QString(), true ) ); if ( bIsReply ) { if ( bCompatMethodAccepted ) { attendee->setStatus( Attendee::Accepted ); } if ( bCompatMethodAcceptedCond ) { attendee->setStatus( Attendee::Declined ); } if ( bCompatMethodDeclined ) { attendee->setStatus( Attendee::Tentative ); } } else { attendee->setStatus( Attendee::NeedsAction ); attendee->setRole( Attendee::ReqParticipant ); } event->addAttendee( attendee ); } } s = tnefMsg->findProp( 0x3ff8 ); // look for organizer property if ( s.isEmpty() && !bIsReply ) { s = sSenderSearchKeyEmail; } // TODO: Use the common name? if ( !s.isEmpty() ) { event->setOrganizer( s ); } s = tnefMsg->findProp( 0x819b ).remove( QChar( '-' ) ).remove( QChar( ':' ) ); event->setDtStart( KDateTime::fromString( s ) ); // ## Format?? s = tnefMsg->findProp( 0x819c ).remove( QChar( '-' ) ).remove( QChar( ':' ) ); event->setDtEnd( KDateTime::fromString( s ) ); s = tnefMsg->findProp( 0x810d ); event->setLocation( s ); // is it OK to set this to OPAQUE always ?? //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme! //vPart += "SEQUENCE:0\n"; // is "0x0023" OK - or should we look for "0x0003" ?? s = tnefMsg->findProp( 0x0023 ); event->setUid( s ); // PENDING(khz): is this value in local timezone? Must it be // adjusted? Most likely this is a bug in the server or in // Outlook - we ignore it for now. s = tnefMsg->findProp( 0x8202 ).remove( QChar( '-' ) ).remove( QChar( ':' ) ); // ### kcal always uses currentDateTime() // event->setDtStamp( QDateTime::fromString( s ) ); s = tnefMsg->findNamedProp( "Keywords" ); event->setCategories( s ); s = tnefMsg->findProp( 0x1000 ); event->setDescription( s ); s = tnefMsg->findProp( 0x0070 ); event->setSummary( s ); s = tnefMsg->findProp( 0x0026 ); event->setPriority( s.toInt() ); // is reminder flag set ? if ( !tnefMsg->findProp( 0x8503 ).isEmpty() ) { Alarm::Ptr alarm( new Alarm( event.data() ) ); // TODO: fix when KCalCore::Alarm is fixed KDateTime highNoonTime = pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 ). remove( QChar( '-' ) ).remove( QChar( ':' ) ) ); KDateTime wakeMeUpTime = pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" ). remove( QChar( '-' ) ).remove( QChar( ':' ) ) ); alarm->setTime( wakeMeUpTime ); if ( highNoonTime.isValid() && wakeMeUpTime.isValid() ) { alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) ); } else { // default: wake them up 15 minutes before the appointment alarm->setStartOffset( Duration( 15 * 60 ) ); } alarm->setDisplayAlarm( i18n( "Reminder" ) ); // Sorry: the different action types are not known (yet) // so we always set 'DISPLAY' (no sounds, no images...) event->addAlarm( alarm ); } //ensure we have a uid for this event if ( event->uid().isEmpty() ) { event->setUid( CalFormat::createUniqueId() ); } cal->addEvent( event ); bOk = true; // we finished composing a vCal } else if ( bCompatClassNote || "IPM.CONTACT" == msgClass ) { addressee.setUid( stringProp( tnefMsg, attMSGID ) ); addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) ); addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true ); addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false ); addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false ); addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) ); addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) ); addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) ); addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) ); addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) ); addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) ); addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) ); QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY ). remove( QChar( '-' ) ).remove( QChar( ':' ) ); if ( !s.isEmpty() ) { addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s ); } addressee.setUrl( KUrl( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE ) ) ); // collect parts of Name entry addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) ); addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) ); addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) ); addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) ); addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) ); addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) ); addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) ); addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) ); /* the MAPI property ID of this (multiline) )field is unknown: vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" ); */ KABC::Address adr; adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) ); adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) ); adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) ); adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) ); adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) ); adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) ); adr.setType( KABC::Address::Home ); addressee.insertAddress( adr ); adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) ); adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) ); adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) ); adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) ); adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) ); adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) ); adr.setType( KABC::Address::Work ); addressee.insertAddress( adr ); adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) ); adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) ); adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) ); adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) ); adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) ); adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) ); adr.setType( KABC::Address::Dom ); addressee.insertAddress( adr ); // problem: the 'other' address was stored by KOrganizer in // a line looking like the following one: // vPart += "\nADR;TYPE=dom;TYPE=intl;TYPE=parcel;TYPE=postal;TYPE=work;" // "TYPE=home:other_pobox;;other_str1\nother_str2;other_loc;other_region;" // "other_pocode;other_country" QString nr; nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER ); addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) ); nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER ); addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) ); nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER ); addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) ); nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER ); addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) ); nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER ); addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) ); s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY ). remove( QChar( '-' ) ).remove( QChar( ':' ) ); if ( !s.isEmpty() ) { addressee.setBirthday( QDateTime::fromString( s ) ); } bOk = ( !addressee.isEmpty() ); } else if ( "IPM.NOTE" == msgClass ) { } // else if ... and so on ... } } // Compose return string // KDAB_TODO: Interesting, without the explicit QString the toString call is // reported to be ambigious with toString( const Incidence::Ptr & ). const QString iCal = calFormat.toString( cal, QString() ); if ( !iCal.isEmpty() ) { // This was an iCal return iCal; } // Not an iCal - try a vCard KABC::VCardConverter converter; return QString::fromUtf8( converter.createVCard( addressee ) ); }
int main( int argc, char **argv ) { KAboutData aboutData( "testincidence", 0, ki18n( "Test Incidence" ), "0.1" ); KCmdLineArgs::init( argc, argv, &aboutData ); KCmdLineOptions options; options.add( "verbose", ki18n( "Verbose output" ) ); KCmdLineArgs::addCmdLineOptions( options ); KComponentData componentData( &aboutData ); //QCoreApplication app( KCmdLineArgs::qtArgc(), KCmdLineArgs::qtArgv() ); KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); bool verbose = false; if ( args->isSet( "verbose" ) ) { verbose = true; } ICalFormat f; Event::Ptr event1 = Event::Ptr( new Event ); event1->setSummary( "Test Event" ); event1->recurrence()->setDaily( 2 ); event1->recurrence()->setDuration( 3 ); QString eventString1 = f.toString( event1.staticCast<Incidence>() ); if ( verbose ) { kDebug() << "EVENT1 START:" << eventString1 << "EVENT1 END"; } event1->setSchedulingID( "foo" ); Incidence::Ptr event2 = Incidence::Ptr( event1->clone() ); Q_ASSERT( event1->uid() == event2->uid() ); Q_ASSERT( event1->schedulingID() == event2->schedulingID() ); QString eventString2 = f.toString( event2.staticCast<Incidence>() ); if ( verbose ) { kDebug() << "EVENT2 START:" << eventString2 << "EVENT2 END"; } if ( eventString1 != eventString2 ) { kDebug() << "Clone Event FAILED."; } else { kDebug() << "Clone Event SUCCEEDED."; } Todo::Ptr todo1 = Todo::Ptr( new Todo ); todo1->setSummary( "Test todo" ); QString todoString1 = f.toString( todo1.staticCast<Incidence>() ); if ( verbose ) { kDebug() << "todo1 START:" << todoString1 << "todo1 END"; } Incidence::Ptr todo2 = Incidence::Ptr( todo1->clone() ); QString todoString2 = f.toString( todo2 ); if ( verbose ) { kDebug() << "todo2 START:" << todoString2 << "todo2 END"; } if ( todoString1 != todoString2 ) { kDebug() << "Clone Todo FAILED."; } else { kDebug() << "Clone Todo SUCCEEDED."; } }