1
/****************************************************************************
3
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4
** Contact: http://www.qt-project.org/legal
6
** This file is part of the QtVersitOrganizer module of the Qt Toolkit.
8
** $QT_BEGIN_LICENSE:LGPL$
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 Digia. For licensing terms and
14
** conditions see http://qt.digia.com/licensing. For further information
15
** use the contact form at http://qt.digia.com/contact-us.
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 as published by the Free Software
20
** Foundation and appearing in the file LICENSE.LGPL included in the
21
** packaging of this file. Please review the following information to
22
** ensure the GNU Lesser General Public License version 2.1 requirements
23
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25
** In addition, as a special exception, Digia gives you certain additional
26
** rights. These rights are described in the Digia Qt LGPL Exception
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29
** GNU General Public License Usage
30
** Alternatively, this file may be used under the terms of the GNU
31
** General Public License version 3.0 as published by the Free Software
32
** Foundation and appearing in the file LICENSE.GPL included in the
33
** packaging of this file. Please review the following information to
34
** ensure the GNU General Public License version 3.0 requirements will be
35
** met: http://www.gnu.org/copyleft/gpl.html.
40
****************************************************************************/
42
#include "qversitorganizerimporter_p.h"
43
#include <qversitdocument.h>
44
#include <qversitproperty.h>
45
#include <qorganizer.h>
46
#include "qversitorganizerdefs_p.h"
47
#include "qversitorganizerpluginloader_p.h"
48
#include <private/qversitutils_p.h>
51
QT_BEGIN_NAMESPACE_VERSITORGANIZER
53
QVersitOrganizerImporterPrivate::QVersitOrganizerImporterPrivate(const QString& profile) :
54
mPropertyHandler(NULL),
55
mTimeZoneHandler(NULL),
56
mDurationSpecified(false)
58
int versitPropertyCount =
59
sizeof(versitOrganizerDetailMappings)/sizeof(VersitOrganizerDetailMapping);
60
for (int i = 0; i < versitPropertyCount; i++) {
61
mPropertyMappings.insert(
62
QLatin1String(versitOrganizerDetailMappings[i].versitPropertyName),
63
QPair<QOrganizerItemDetail::DetailType, int>(
64
versitOrganizerDetailMappings[i].detailType,
65
versitOrganizerDetailMappings[i].detailField));
68
mPluginPropertyHandlers = QVersitOrganizerPluginLoader::instance()->createOrganizerHandlers(profile);
69
mTimeZoneHandler = QVersitOrganizerPluginLoader::instance()->timeZoneHandler();
72
QVersitOrganizerImporterPrivate::~QVersitOrganizerImporterPrivate()
74
foreach (QVersitOrganizerHandler* pluginHandler, mPluginPropertyHandlers) {
79
bool QVersitOrganizerImporterPrivate::importDocument(
80
const QVersitDocument& topLevel,
81
const QVersitDocument& subDocument,
83
QVersitOrganizerImporter::Error* error)
85
if (subDocument.componentType() == QStringLiteral("VEVENT")) {
86
item->setType(QOrganizerItemType::TypeEvent);
87
} else if (subDocument.componentType() == QStringLiteral("VTODO")) {
88
item->setType(QOrganizerItemType::TypeTodo);
89
} else if (subDocument.componentType() == QStringLiteral("VJOURNAL")) {
90
item->setType(QOrganizerItemType::TypeJournal);
91
} else if (subDocument.componentType() == QStringLiteral("VTIMEZONE")) {
92
mTimeZones.addTimeZone(importTimeZone(subDocument));
93
*error = QVersitOrganizerImporter::NoError;
96
*error = QVersitOrganizerImporter::InvalidDocumentError;
99
const QList<QVersitProperty> properties = subDocument.properties();
100
if ( (subDocument.subDocuments().isEmpty()) && (properties.isEmpty())) {
101
*error = QVersitOrganizerImporter::EmptyDocumentError;
104
foreach (const QVersitProperty& property, properties) {
105
importProperty(subDocument, property, item);
108
if (!subDocument.subDocuments().isEmpty()) {
109
foreach (const QVersitDocument &nestedSubDoc, subDocument.subDocuments())
110
foreach (const QVersitProperty &nestedProp, nestedSubDoc.properties()) {
111
importProperty(nestedSubDoc, nestedProp, item);
112
if (nestedSubDoc.componentType() == QStringLiteral("VALARM"))
116
// run plugin handlers
117
foreach (QVersitOrganizerImporterPropertyHandler* handler, mPluginPropertyHandlers) {
118
handler->subDocumentProcessed(topLevel, subDocument, item);
120
// run property handlers
121
if (mPropertyHandler) {
122
mPropertyHandler->subDocumentProcessed(topLevel, subDocument, item);
127
void QVersitOrganizerImporterPrivate::importProperty(
128
const QVersitDocument& document,
129
const QVersitProperty& property,
130
QOrganizerItem* item)
132
QList<QOrganizerItemDetail> updatedDetails;
134
bool success = false;
135
if (property.name() == QStringLiteral("CREATED")) {
136
success = createTimestampCreated(property, item, &updatedDetails);
137
} else if (property.name() == QStringLiteral("LAST-MODIFIED")) {
138
success = createTimestampModified(property, item, &updatedDetails);
139
} else if (property.name() == QStringLiteral("X-QTPROJECT-VERSION")) {
140
success = createVersion(property, item, &updatedDetails);
141
} else if (property.name() == QStringLiteral("PRIORITY")) {
142
success = createPriority(property, item, &updatedDetails);
143
} else if (property.name() == QStringLiteral("COMMENT")) {
144
success = createComment(property, &updatedDetails);
145
} else if (property.name() == QStringLiteral("X-QTPROJECT-EXTENDED-DETAIL")) {
146
success = createExtendedDetail(property, &updatedDetails);
147
} else if (mPropertyMappings.contains(property.name())) {
148
success = createSimpleDetail(property, item, &updatedDetails);
149
} else if (document.componentType() == QStringLiteral("VEVENT")) {
150
if (property.name() == QStringLiteral("DTSTART")) {
151
success = createStartDateTime(property, item, &updatedDetails);
152
} else if (property.name() == QStringLiteral("DTEND")) {
153
success = createEndDateTime(property, item, &updatedDetails);
154
} else if (property.name() == QStringLiteral("DURATION")) {
155
success = createDuration(property, item, &updatedDetails);
156
} else if (property.name() == QStringLiteral("RRULE")
157
|| (property.name() == QStringLiteral("EXRULE"))) {
158
success = createRecurrenceRule(property, item, &updatedDetails);
159
} else if (property.name() == QStringLiteral("RDATE")
160
|| (property.name() == QStringLiteral("EXDATE"))) {
161
success = createRecurrenceDates(property, item, &updatedDetails);
162
} else if (property.name() == QStringLiteral("RECURRENCE-ID")) {
163
success = createRecurrenceId(property, item, &updatedDetails);
165
} else if (document.componentType() == QStringLiteral("VTODO")) {
166
if (property.name() == QStringLiteral("DTSTART")) {
167
success = createTodoStartDateTime(property, item, &updatedDetails);
168
} else if (property.name() == QStringLiteral("DUE")) {
169
success = createDueDateTime(property, item, &updatedDetails);
170
} else if (property.name() == QStringLiteral("RRULE")
171
|| (property.name() == QStringLiteral("EXRULE"))) {
172
success = createRecurrenceRule(property, item, &updatedDetails);
173
} else if (property.name() == QStringLiteral("RDATE")
174
|| (property.name() == QStringLiteral("EXDATE"))) {
175
success = createRecurrenceDates(property, item, &updatedDetails);
176
} else if (property.name() == QStringLiteral("STATUS")) {
177
success = createStatus(property, item, &updatedDetails);
178
} else if (property.name() == QStringLiteral("PERCENT-COMPLETE")) {
179
success = createPercentageComplete(property, item, &updatedDetails);
180
} else if (property.name() == QStringLiteral("COMPLETED")) {
181
success = createFinishedDateTime(property, item, &updatedDetails);
182
} else if (property.name() == QStringLiteral("RECURRENCE-ID")) {
183
success = createRecurrenceId(property, item, &updatedDetails);
185
} else if (document.componentType() == QStringLiteral("VALARM")) {
186
success = createItemReminder(document, item, &updatedDetails);
187
} else if (document.componentType() == QStringLiteral("VJOURNAL")) {
188
if (property.name() == QStringLiteral("DTSTART")) {
189
success = createJournalEntryDateTime(property, item, &updatedDetails);
193
// run plugin handlers
194
foreach (QVersitOrganizerImporterPropertyHandler* handler, mPluginPropertyHandlers) {
195
handler->propertyProcessed(document, property, *item, &success, &updatedDetails);
197
// run the handler, if set
198
if (mPropertyHandler) {
199
mPropertyHandler->propertyProcessed(document, property, *item, &success, &updatedDetails);
202
foreach (QOrganizerItemDetail detail, updatedDetails) {
203
item->saveDetail(&detail);
207
bool QVersitOrganizerImporterPrivate::createSimpleDetail(
208
const QVersitProperty& property,
209
QOrganizerItem* item,
210
QList<QOrganizerItemDetail>* updatedDetails)
212
if (property.value().isEmpty())
214
QPair<QOrganizerItemDetail::DetailType, int> mapping = mPropertyMappings[property.name()];
215
QOrganizerItemDetail::DetailType definitionName = mapping.first;
216
int fieldName = mapping.second;
217
QOrganizerItemDetail detail(item->detail(definitionName));
218
if (detail.isEmpty())
219
detail = QOrganizerItemDetail(definitionName);
220
detail.setValue(fieldName, property.value());
221
updatedDetails->append(detail);
225
bool QVersitOrganizerImporterPrivate::createTimestampCreated(
226
const QVersitProperty& property,
227
QOrganizerItem* item,
228
QList<QOrganizerItemDetail>* updatedDetails) {
229
if (property.value().isEmpty())
231
QDateTime datetime = parseDateTime(property);
232
if (!datetime.isValid())
234
QOrganizerItemTimestamp timestamp(item->detail(QOrganizerItemDetail::TypeTimestamp));
235
timestamp.setCreated(datetime);
236
updatedDetails->append(timestamp);
240
bool QVersitOrganizerImporterPrivate::createTimestampModified(
241
const QVersitProperty& property,
242
QOrganizerItem* item,
243
QList<QOrganizerItemDetail>* updatedDetails) {
244
if (property.value().isEmpty())
246
QDateTime datetime = parseDateTime(property);
247
if (!datetime.isValid())
249
QOrganizerItemTimestamp timestamp(item->detail(QOrganizerItemDetail::TypeTimestamp));
250
timestamp.setLastModified(datetime);
251
updatedDetails->append(timestamp);
256
* Takes the first value in \a list, or an empty QString is if the list is empty.
258
QString QVersitOrganizerImporterPrivate::takeFirst(QList<QString>& list) const
260
return list.empty() ? QString() : list.takeFirst();
263
bool QVersitOrganizerImporterPrivate::createVersion(
264
const QVersitProperty& property,
265
QOrganizerItem* item,
266
QList<QOrganizerItemDetail>* updatedDetails) {
268
QVariant variant = property.variantValue();
269
if (property.valueType() != QVersitProperty::CompoundType
270
|| variant.type() != QVariant::StringList) {
273
QStringList values = variant.toStringList();
276
QOrganizerItemVersion version(item->detail(QOrganizerItemDetail::TypeVersion));
277
version.setVersion(takeFirst(values).toInt(&conversionOk));
278
QString extendedVersionString = takeFirst(values);
279
if (!extendedVersionString.isEmpty())
280
version.setExtendedVersion(extendedVersionString.toLocal8Bit());
283
updatedDetails->append(version);
290
bool QVersitOrganizerImporterPrivate::createPriority(
291
const QVersitProperty& property,
292
QOrganizerItem* item,
293
QList<QOrganizerItemDetail>* updatedDetails) {
294
if (property.value().length() != 1)
298
int p = property.value().toInt(&ok);
301
QOrganizerItemPriority priority(item->detail(QOrganizerItemDetail::TypePriority));
302
priority.setPriority(QOrganizerItemPriority::Priority(p));
303
updatedDetails->append(priority);
307
bool QVersitOrganizerImporterPrivate::createComment(
308
const QVersitProperty& property,
309
QList<QOrganizerItemDetail>* updatedDetails) {
310
if (property.value().isEmpty())
312
QOrganizerItemComment comment;
313
comment.setComment(property.value());
314
updatedDetails->append(comment);
318
bool QVersitOrganizerImporterPrivate::createItemReminder(
319
const QVersitDocument& valarmDocument,
320
QOrganizerItem* item,
321
QList<QOrganizerItemDetail>* updatedDetails)
323
QOrganizerItemReminder itemReminder;
326
int secondsBeforeStart;
327
bool alreadySetSecondsBeforeStart = false;
328
const QList<QVersitProperty> valarmProperties = valarmDocument.properties();
330
QVariantList attachValues;
331
QString descriptionValue;
332
QString summaryValue;
333
QStringList attendees;
335
foreach (const QVersitProperty &valarmProperty, valarmProperties) {
336
if (valarmProperty.name() == QStringLiteral("TRIGGER")) {
337
secondsBeforeStart = triggerToSecondsBeforeStart(valarmProperty, *item);
338
alreadySetSecondsBeforeStart = true;
339
} else if (valarmProperty.name() == QStringLiteral("REPEAT")) {
340
repetitionCount = valarmProperty.value().toInt();
341
} else if (valarmProperty.name() == QStringLiteral("DURATION")) {
342
repetitionDelay = Duration::parseDuration(valarmProperty.value()).toSeconds();
343
} else if (valarmProperty.name() == QStringLiteral("ACTION")) {
344
actionValue = valarmProperty.value().toUpper();
345
} else if (valarmProperty.name() == QStringLiteral("ATTACH")) {
346
attachValues.append(valarmProperty.variantValue());
347
} else if (valarmProperty.name() == QStringLiteral("DESCRIPTION")) {
348
descriptionValue = valarmProperty.value();
349
} else if (valarmProperty.name() == QStringLiteral("SUMMARY")) {
350
summaryValue = valarmProperty.value();
351
} else if (valarmProperty.name() == QStringLiteral("ATTENDEE")) {
352
attendees << valarmProperty.value();
355
if ((actionValue.isEmpty()) || (!alreadySetSecondsBeforeStart) ) {
356
//ACTION and TRIGGER are mandatory in a VALARM component.
358
} else if (actionValue == QStringLiteral("AUDIO")) {
359
QOrganizerItemAudibleReminder audibleReminder;
360
audibleReminder.setRepetition(repetitionCount, repetitionDelay);
361
audibleReminder.setSecondsBeforeStart(secondsBeforeStart);
362
if (!attachValues.isEmpty())
363
audibleReminder.setDataUrl(QUrl(attachValues.first().toString()));
364
updatedDetails->append(audibleReminder);
366
} else if (actionValue == QStringLiteral("DISPLAY")) {
367
if (descriptionValue.isNull())
368
return false;//Invalid since REQUIRED properties are not found
369
QOrganizerItemVisualReminder visualReminder;
370
visualReminder.setRepetition(repetitionCount, repetitionDelay);
371
visualReminder.setSecondsBeforeStart(secondsBeforeStart);
372
if (!descriptionValue.isEmpty()) {
373
visualReminder.setMessage(descriptionValue);
374
updatedDetails->append(visualReminder);
379
} else if (actionValue == QStringLiteral("EMAIL")) {
380
if (descriptionValue.isNull() || summaryValue.isNull() || attendees.isEmpty())
381
return false;//Invalid since REQUIRED properties are not found
382
QOrganizerItemEmailReminder emailReminder;
383
emailReminder.setRepetition(repetitionCount, repetitionDelay);
384
emailReminder.setSecondsBeforeStart(secondsBeforeStart);
385
emailReminder.setValue(QOrganizerItemEmailReminder::FieldBody, descriptionValue);
386
emailReminder.setValue(QOrganizerItemEmailReminder::FieldSubject, summaryValue);
387
if (!attachValues.isEmpty())
388
emailReminder.setValue(QOrganizerItemEmailReminder::FieldAttachments, attachValues);
389
emailReminder.setContents(summaryValue, descriptionValue, attachValues);
390
emailReminder.setRecipients(attendees);
391
updatedDetails->append(emailReminder);
393
} else { //ACTION property had an invalid value
398
int QVersitOrganizerImporterPrivate::triggerToSecondsBeforeStart(const QVersitProperty &triggerProperty, const QOrganizerItem &item)
402
//The default value type for TRIGGER property is DURATION.
403
bool encodedAsDuration = true;
405
if (!triggerProperty.parameters().isEmpty()) {
406
const QString triggerValue = triggerProperty.parameters().value(QStringLiteral("VALUE")).toUpper();
407
if (triggerValue == QStringLiteral("DATE-TIME"))
408
encodedAsDuration = false;
409
else if ( (!triggerValue.isEmpty()) &&
410
(triggerValue != QStringLiteral("DURATION")) ) {
411
return 0;//Invalid trigger property...just return default value.
415
if (encodedAsDuration) {
416
const QString related = triggerProperty.parameters().value(QStringLiteral("RELATED")).toUpper();
417
result = Duration::parseDuration(triggerProperty.value()).toSeconds();
418
switch (item.type()) {
419
case QOrganizerItemType::TypeTodo:
420
case QOrganizerItemType::TypeTodoOccurrence: {
421
if (related == QStringLiteral("START")) {
422
QOrganizerTodoTime todoTime = item.detail(QOrganizerItemDetail::TypeTodoTime);
423
QDateTime relativeTrigger = todoTime.startDateTime().addSecs(result);
424
result = relativeTrigger.secsTo(todoTime.dueDateTime());
425
} else if ( (related.isEmpty()) || (related == QStringLiteral("END")) ) {
426
result = (-1 * result);
430
case QOrganizerItemType::TypeEvent:
431
case QOrganizerItemType::TypeEventOccurrence: {
432
if (related == QStringLiteral("END")) {
433
QOrganizerEventTime eventTime = item.detail(QOrganizerItemDetail::TypeEventTime);
434
QDateTime relativeTrigger = eventTime.endDateTime().addSecs(result);
435
result = relativeTrigger.secsTo(eventTime.startDateTime());
436
} else if ( (related.isEmpty()) || (related == QStringLiteral("START")) ) {
437
result = (-1 * result);
445
QDateTime absoluteTrigger = parseDateTime(triggerProperty.value());
446
switch (item.type()) {
447
case QOrganizerItemType::TypeTodo:
448
case QOrganizerItemType::TypeTodoOccurrence: {
449
QOrganizerTodoTime todoTime = item.detail(QOrganizerItemDetail::TypeTodoTime);
450
result = absoluteTrigger.secsTo(todoTime.dueDateTime());
453
case QOrganizerItemType::TypeEvent:
454
case QOrganizerItemType::TypeEventOccurrence: {
455
QOrganizerEventTime eventTime = item.detail(QOrganizerItemDetail::TypeEventTime);
456
result = absoluteTrigger.secsTo(eventTime.startDateTime());
463
return result >= 0 ? result: 0;
466
bool QVersitOrganizerImporterPrivate::createExtendedDetail(
467
const QVersitProperty &property,
468
QList<QOrganizerItemDetail> *updatedDetails) {
469
QOrganizerItemExtendedDetail extendedDetail;
470
const QVariant variant = property.variantValue();
471
if (property.valueType() != QVersitProperty::CompoundType
472
|| variant.type() != QVariant::StringList)
475
QStringList values = variant.toStringList();
476
extendedDetail.setName(takeFirst(values));
478
if (VersitUtils::convertFromJson(takeFirst(values), &data))
479
extendedDetail.setData(data);
483
updatedDetails->append(extendedDetail);
487
bool QVersitOrganizerImporterPrivate::createRecurrenceId(
488
const QVersitProperty& property,
489
QOrganizerItem* item,
490
QList<QOrganizerItemDetail>* updatedDetails) {
491
QDate date = parseDate(property.value());
494
QOrganizerItemParent origin(item->detail(QOrganizerItemDetail::TypeParent));
495
origin.setOriginalDate(date);
496
updatedDetails->append(origin);
497
item->setType(QOrganizerItemType::TypeEventOccurrence);
501
/*! Set the startDateTime field of the EventTimeRange detail. If the end date has been set from a
502
* DURATION, it will be updated.
504
bool QVersitOrganizerImporterPrivate::createStartDateTime(
505
const QVersitProperty& property,
506
QOrganizerItem* item,
507
QList<QOrganizerItemDetail>* updatedDetails) {
508
if (property.value().isEmpty())
511
QDateTime newStart = parseDateTime(property, &hasTime);
512
if (!newStart.isValid())
514
QOrganizerEventTime etr(item->detail(QOrganizerItemDetail::TypeEventTime));
515
if (mDurationSpecified) {
516
// Need to fix up the end date to match the duration of the event
517
QDateTime start = etr.startDateTime();
518
QDateTime end = etr.endDateTime();
519
if (!start.isValid()) {
520
// not having a start date set is treated as a start date of epoch
521
start = QDateTime(QDate(1970, 1, 1));
523
// newEnd = end + (newStart - start)
524
int durationDays = start.daysTo(newStart);
525
QDateTime newEnd = end.addDays(durationDays);
526
int durationSecs = start.addDays(durationDays).secsTo(newStart);
527
newEnd = newEnd.addSecs(durationSecs);
528
etr.setEndDateTime(newEnd);
530
etr.setStartDateTime(newStart);
531
if (!etr.isAllDay() && !hasTime)
533
updatedDetails->append(etr);
537
/*! Set the endDateTime field of the EventTimeRange detail.
539
bool QVersitOrganizerImporterPrivate::createEndDateTime(
540
const QVersitProperty& property,
541
QOrganizerItem* item,
542
QList<QOrganizerItemDetail>* updatedDetails) {
543
if (property.value().isEmpty())
546
QDateTime newEnd = parseDateTime(property, &hasTime);
547
if (!newEnd.isValid())
549
QOrganizerEventTime etr(item->detail(QOrganizerItemDetail::TypeEventTime));
550
if (!etr.isAllDay() && !hasTime)
553
// In iCalendar, the end date is exclusive while in Qt Organizer, it is inclusive.
555
etr.setEndDateTime(newEnd.addDays(-1));
557
etr.setEndDateTime(newEnd);
559
updatedDetails->append(etr);
560
mDurationSpecified = false;
565
/*! Sets the endDateTime field of the EventTimeRange detail using a DURATION property.
567
bool QVersitOrganizerImporterPrivate::createDuration(
568
const QVersitProperty& property,
569
QOrganizerItem* item,
570
QList<QOrganizerItemDetail>* updatedDetails) {
571
if (property.value().isEmpty())
573
Duration duration = Duration::parseDuration(property.value());
574
if (!duration.isValid())
576
QOrganizerEventTime etr(item->detail(QOrganizerItemDetail::TypeEventTime));
577
QDateTime startTime = etr.startDateTime();
578
if (!startTime.isValid()) {
579
// not having a start date set is treated as a start date of epoch
580
startTime = QDateTime(QDate(1970, 1, 1));
583
startTime.addDays(7*duration.weeks() + duration.days())
584
.addSecs(3600*duration.hours() + 60*duration.minutes() + duration.seconds()));
585
updatedDetails->append(etr);
586
mDurationSpecified = true;
590
/*! Set the StartDateTime field of the TodoTimeRange detail.
592
bool QVersitOrganizerImporterPrivate::createTodoStartDateTime(
593
const QVersitProperty& property,
594
QOrganizerItem* item,
595
QList<QOrganizerItemDetail>* updatedDetails) {
596
if (property.value().isEmpty())
599
QDateTime newStart = parseDateTime(property, &hasTime);
600
if (!newStart.isValid())
602
QOrganizerTodoTime ttr(item->detail(QOrganizerItemDetail::TypeTodoTime));
603
ttr.setStartDateTime(newStart);
604
if (!ttr.isAllDay() && !hasTime)
606
updatedDetails->append(ttr);
610
/*! Set the DueDateTime field of the TodoTimeRange detail.
612
bool QVersitOrganizerImporterPrivate::createDueDateTime(
613
const QVersitProperty& property,
614
QOrganizerItem* item,
615
QList<QOrganizerItemDetail>* updatedDetails) {
616
if (property.value().isEmpty())
619
QDateTime newEnd = parseDateTime(property, &hasTime);
620
if (!newEnd.isValid())
622
QOrganizerTodoTime ttr(item->detail(QOrganizerItemDetail::TypeTodoTime));
623
ttr.setDueDateTime(newEnd);
624
if (!ttr.isAllDay() && !hasTime)
626
updatedDetails->append(ttr);
627
mDurationSpecified = false;
631
/*! Set the EntryDateTime field of the JournalTimeRange detail.
633
bool QVersitOrganizerImporterPrivate::createJournalEntryDateTime(
634
const QVersitProperty& property,
635
QOrganizerItem* item,
636
QList<QOrganizerItemDetail>* updatedDetails) {
637
if (property.value().isEmpty())
639
QDateTime dateTime = parseDateTime(property);
640
if (!dateTime.isValid())
642
QOrganizerJournalTime jtr(item->detail(QOrganizerItemDetail::TypeJournalTime));
643
jtr.setEntryDateTime(dateTime);
644
updatedDetails->append(jtr);
648
/*! Parses a datetime stored in the \a property as an ISO 8601 datetime in basic format, either in
649
* UTC time zone, floating time zone, or (if a TZID parameter exists in \a property), as a foreign
650
* time zone (returned as a UTC datetime). Returns an invalid QDateTime if the string cannot be
653
* \a hasTime is set to true if the parsed date-time has a time, or false if it is a date only.
654
* (the time portion is set to some valid but arbitrary value).
656
QDateTime QVersitOrganizerImporterPrivate::parseDateTime(const QVersitProperty& property,
659
const QMultiHash<QString, QString> parameters = property.parameters();
660
if (parameters.find(QStringLiteral("VALUE"), QStringLiteral("DATE")) == parameters.constEnd()) {
661
// try parsing a datetime
664
QDateTime datetime(parseDateTime(property.value()));
665
if (datetime.isValid() && datetime.timeSpec() == Qt::LocalTime) {
666
QMultiHash<QString, QString> params = property.parameters();
667
QString tzid = params.value(QStringLiteral("TZID"));
668
if (!tzid.isEmpty()) {
669
if (tzid.at(0) == QLatin1Char('/') && mTimeZoneHandler)
670
datetime = mTimeZoneHandler->convertTimeZoneToUtc(datetime, tzid);
672
datetime = mTimeZones.convert(datetime, tzid);
680
retn.setDate(QDate::fromString(property.value(), QStringLiteral("yyyyMMdd")));
681
retn.setTime(QTime(0, 0, 0));
686
/*! Parses \a str as an ISO 8601 datetime in basic format, either in UTC timezone or floating
687
* timezone. Returns an invalid QDateTime if the string cannot be parsed.
689
QDateTime QVersitOrganizerImporterPrivate::parseDateTime(QString str) const
691
bool utc = str.endsWith(QLatin1Char('Z'), Qt::CaseInsensitive);
693
str.chop(1); // take away z from end;
694
QDateTime dt(QDateTime::fromString(str, QStringLiteral("yyyyMMddTHHmmss")));
696
dt.setTimeSpec(Qt::UTC);
701
* Imports a RRULE, EXRULE, RDATE or EXDATE property
703
bool QVersitOrganizerImporterPrivate::createRecurrenceRule(
704
const QVersitProperty& property,
705
QOrganizerItem* item,
706
QList<QOrganizerItemDetail>* updatedDetails) {
707
if (property.value().isEmpty())
709
QOrganizerRecurrenceRule rule;
710
if (!parseRecurRule(property.value(), &rule))
712
QOrganizerItemRecurrence detail(item->detail(QOrganizerItemDetail::TypeRecurrence));
713
if (property.name() == QStringLiteral("RRULE")) {
714
detail.setRecurrenceRules(detail.recurrenceRules() << rule);
715
} else if (property.name() == QStringLiteral("EXRULE")) {
716
detail.setExceptionRules(detail.exceptionRules() << rule);
718
updatedDetails->append(detail);
723
* Parses an iCalendar recurrence rule string \a str and puts the result in \a rule.
724
* Return true on success, false on failure.
726
bool QVersitOrganizerImporterPrivate::parseRecurRule(const QString& str, QOrganizerRecurrenceRule* rule) const
728
QStringList parts = str.split(QLatin1Char(';'));
729
if (parts.size() == 0)
732
QString freqPart = parts.takeFirst();
733
QStringList freqParts = freqPart.split(QLatin1Char('='));
734
if (freqParts.size() != 2)
736
if (freqParts.at(0) != QStringLiteral("FREQ"))
738
QString freqValue = freqParts.at(1);
739
if (freqValue == QStringLiteral("DAILY")) {
740
rule->setFrequency(QOrganizerRecurrenceRule::Daily);
741
} else if (freqValue == QStringLiteral("WEEKLY")) {
742
rule->setFrequency(QOrganizerRecurrenceRule::Weekly);
743
} else if (freqValue == QStringLiteral("MONTHLY")) {
744
rule->setFrequency(QOrganizerRecurrenceRule::Monthly);
745
} else if (freqValue == QStringLiteral("YEARLY")) {
746
rule->setFrequency(QOrganizerRecurrenceRule::Yearly);
751
foreach (const QString& part, parts) {
752
QStringList keyValue = part.split(QLatin1Char('='));
753
if (keyValue.size() != 2)
755
parseRecurFragment(keyValue.at(0), keyValue.at(1), rule);
761
* Parses a fragment of an iCalendar string (the part between the semicolons) and updates \a rule.
762
* \a key is the part of the fragment before the equals sign and \a value is the part after.
764
void QVersitOrganizerImporterPrivate::parseRecurFragment(const QString& key, const QString& value,
765
QOrganizerRecurrenceRule* rule) const
767
if (key == QStringLiteral("INTERVAL")) {
769
int n = value.toInt(&ok);
771
rule->setInterval(n);
772
} else if (key == QStringLiteral("COUNT")) {
774
int count = value.toInt(&ok);
775
if (ok && count >= 0) {
776
rule->setLimit(count);
778
} else if (key == QStringLiteral("UNTIL")) {
780
if (value.contains(QLatin1Char('T'))) {
781
QDateTime dt = parseDateTime(value);
784
date = QDate::fromString(value, QStringLiteral("yyyyMMdd"));
787
rule->setLimit(date);
788
} else if (key == QStringLiteral("BYDAY")) {
789
QSet<Qt::DayOfWeek> days;
790
QStringList dayParts = value.split(QLatin1Char(','));
791
foreach (QString dayStr, dayParts) {
792
if (dayStr.length() < 2) {
795
} else if (dayStr.length() > 2) {
796
// parse something like -2SU, meaning the second-last Sunday
797
QString posStr = dayStr;
798
dayStr = dayStr.right(2); // dayStr = last two chars
799
posStr.chop(2); // posStr = all except last two chars
801
int pos = posStr.toInt(&ok);
804
rule->setPositions(QSet<int>() << pos);
806
int day = parseDayOfWeek(dayStr);
808
days << (Qt::DayOfWeek)day;
811
if (!days.isEmpty()) {
812
rule->setDaysOfWeek(days);
814
} else if (key == QStringLiteral("BYMONTHDAY")) {
815
QSet<int> days = parseInts(value, -31, 31);
816
if (!days.isEmpty()) {
817
rule->setDaysOfMonth(days);
819
} else if (key == QStringLiteral("BYWEEKNO")) {
820
QSet<int> weeks = parseInts(value, -53, 53);
821
if (!weeks.isEmpty()) {
822
rule->setWeeksOfYear(weeks);
824
} else if (key == QStringLiteral("BYMONTH")) {
825
QSet<QOrganizerRecurrenceRule::Month> months;
826
QStringList monthParts = value.split(QLatin1Char(','));
827
foreach (const QString& monthPart, monthParts) {
829
int month = monthPart.toInt(&ok);
830
if (ok && month >= 1 && month <= 12) {
831
months << (QOrganizerRecurrenceRule::Month)month;
834
if (!months.isEmpty()) {
835
rule->setMonthsOfYear(months);
837
} else if (key == QStringLiteral("BYYEARDAY")) {
838
QSet<int> days = parseInts(value, -366, 366);
839
if (!days.isEmpty()) {
840
rule->setDaysOfYear(days);
842
} else if (key == QStringLiteral("BYSETPOS")) {
843
QSet<int> poss = parseInts(value, -366, 366);
844
if (!poss.isEmpty()) {
845
rule->setPositions(poss);
847
} else if (key == QStringLiteral("WKST")) {
848
int day = parseDayOfWeek(value);
850
rule->setFirstDayOfWeek((Qt::DayOfWeek)day);
856
* Parses and returns a comma-separated list of integers. Only non-zero values between \a min and
857
* \a max (inclusive) are added
859
QSet<int> QVersitOrganizerImporterPrivate::parseInts(const QString& str, int min, int max) const
862
QStringList parts = str.split(QLatin1Char(','));
863
foreach (const QString& part, parts) {
865
int value = part.toInt(&ok);
866
if (ok && value >= min && value <= max && value != 0) {
874
* Parses an iCalendar two-character string representing a day of week and returns an int
875
* corresponding to Qt::DayOfWeek. Returns -1 on parse failure.
877
int QVersitOrganizerImporterPrivate::parseDayOfWeek(const QString& str) const
879
if (str == QStringLiteral("MO")) {
881
} else if (str == QStringLiteral("TU")) {
883
} else if (str == QStringLiteral("WE")) {
884
return Qt::Wednesday;
885
} else if (str == QStringLiteral("TH")) {
887
} else if (str == QStringLiteral("FR")) {
889
} else if (str == QStringLiteral("SA")) {
891
} else if (str == QStringLiteral("SU")) {
899
* Parses an iCalendar RDATE or EXDATE property and updates the recurrenceDates or
900
* exceptionDates in the Recurrence detail.
902
bool QVersitOrganizerImporterPrivate::createRecurrenceDates(
903
const QVersitProperty& property,
904
QOrganizerItem* item,
905
QList<QOrganizerItemDetail>* updatedDetails)
907
if (property.value().isEmpty())
910
if (!parseDates(property.value(), &dates))
912
QOrganizerItemRecurrence detail(item->detail(QOrganizerItemDetail::TypeRecurrence));
913
if (property.name() == QStringLiteral("RDATE")) {
914
detail.setRecurrenceDates(detail.recurrenceDates() + dates);
915
} else if (property.name() == QStringLiteral("EXDATE")) {
916
detail.setExceptionDates(detail.exceptionDates() + dates);
918
updatedDetails->append(detail);
923
* Parses a string like "19970304,19970504,19970704" into a list of QDates
925
bool QVersitOrganizerImporterPrivate::parseDates(const QString& str, QSet<QDate>* dates) const
927
QStringList parts = str.split(QLatin1Char(','));
928
if (parts.size() == 0)
931
foreach (QString part, parts) {
932
QDate date = parseDate(part);
942
* Parses a date in either yyyyMMdd or yyyyMMddTHHmmss format (in the latter case, ignoring the
945
QDate QVersitOrganizerImporterPrivate::parseDate(QString str) const
947
int tIndex = str.indexOf(QLatin1Char('T'));
949
str = str.left(tIndex);
951
return QDate::fromString(str, QStringLiteral("yyyyMMdd"));
954
bool QVersitOrganizerImporterPrivate::createStatus(
955
const QVersitProperty& property,
956
QOrganizerItem* item,
957
QList<QOrganizerItemDetail>* updatedDetails) {
958
QOrganizerTodoProgress::Status status;
959
if (property.value() == QStringLiteral("COMPLETED"))
960
status = QOrganizerTodoProgress::StatusComplete;
961
else if (property.value() == QStringLiteral("NEEDS-ACTION"))
962
status = QOrganizerTodoProgress::StatusNotStarted;
963
else if (property.value() == QStringLiteral("IN-PROCESS"))
964
status = QOrganizerTodoProgress::StatusInProgress;
968
QOrganizerTodoProgress progress(item->detail(QOrganizerItemDetail::TypeTodoProgress));
969
progress.setStatus(status);
970
updatedDetails->append(progress);
974
bool QVersitOrganizerImporterPrivate::createPercentageComplete(
975
const QVersitProperty& property,
976
QOrganizerItem* item,
977
QList<QOrganizerItemDetail>* updatedDetails) {
979
int percent = property.value().toInt(&ok);
983
QOrganizerTodoProgress progress(item->detail(QOrganizerItemDetail::TypeTodoProgress));
984
progress.setPercentageComplete(percent);
985
updatedDetails->append(progress);
989
bool QVersitOrganizerImporterPrivate::createFinishedDateTime(
990
const QVersitProperty& property,
991
QOrganizerItem* item,
992
QList<QOrganizerItemDetail>* updatedDetails) {
993
if (property.value().isEmpty())
995
QDateTime datetime = parseDateTime(property);
996
if (!datetime.isValid())
998
QOrganizerTodoProgress progress(item->detail(QOrganizerItemDetail::TypeTodoProgress));
999
progress.setFinishedDateTime(datetime);
1000
updatedDetails->append(progress);
1004
/*! Parse the iCalendar duration string \a str in an RDP fashion with a two symbol lookahead, and
1005
* returns a Duration that represents it. */
1006
Duration Duration::parseDuration(QString str)
1008
QString token = nextToken(&str);
1009
if (token.isEmpty())
1010
return invalidDuration();
1013
// Accept a + or - if present
1014
if (token == QStringLiteral("+")) {
1015
token = nextToken(&str);
1016
} else if (token == QStringLiteral("-")) {
1017
dur.setNegative(true);
1018
token = nextToken(&str);
1019
} else if (token.isEmpty()) {
1020
return invalidDuration();
1022
// There was no + or - so keep parsing
1026
if (token != QStringLiteral("P")) {
1027
return invalidDuration();
1030
token = nextToken(&str);
1031
if (token.isEmpty()) {
1032
return invalidDuration();
1033
} else if (token == QStringLiteral("T")) {
1035
parseDurationTime(&str, &dur);
1036
} else if (token.at(0).isDigit()) {
1037
// it's either a date or a week - we're not sure yet
1038
int value = token.toInt(); // always succeeds because nextToken next returns a mix of digits/nondigits
1039
token = nextToken(&str);
1040
if (token == QStringLiteral("D")) {
1043
token = nextToken(&str);
1044
// dates optionally define a time
1045
if (token == QStringLiteral("T"))
1046
parseDurationTime(&str, &dur);
1047
} else if (token == QStringLiteral("W")) {
1048
dur.setWeeks(value);
1050
return invalidDuration();
1053
return invalidDuration();
1056
// check that there aren't extra characters on the end
1058
dur.setValid(false);
1063
/*! Parse a duration string starting from after the "T" character. Removes parsed part from \a str
1064
* and updates \a dur with the findings.
1066
void Duration::parseDurationTime(QString* str, Duration* dur)
1068
QString token = nextToken(str);
1069
if (token.isEmpty() || !token.at(0).isDigit())
1070
dur->setValid(false);
1072
int value = token.toInt(); // always succeeds
1074
token = nextToken(str);
1075
if (token == QStringLiteral("H")) {
1076
dur->setHours(value);
1077
if (!str->isEmpty())
1078
parseDurationMinutes(str, dur);
1079
} else if (token == QStringLiteral("M")) {
1080
dur->setMinutes(value);
1081
if (!str->isEmpty())
1082
parseDurationSeconds(str, dur);
1083
} else if (token == QStringLiteral("S")) {
1084
dur->setSeconds(value);
1088
/*! Parse a duration string starting from the part describing the number of minutes. Removes parsed
1089
* part from \a str and updates \a dur with the findings.
1091
void Duration::parseDurationMinutes(QString* str, Duration* dur)
1093
QString token = nextToken(str);
1094
if (token.isEmpty() || !token.at(0).isDigit())
1095
dur->setValid(false);
1097
int value = token.toInt(); // always succeeds
1098
token = nextToken(str);
1099
if (token != QStringLiteral("M")) {
1100
dur->setValid(false);
1103
dur->setMinutes(value);
1105
if (!str->isEmpty())
1106
parseDurationSeconds(str, dur);
1109
/*! Parse a duration string starting from the part describing the number of seconds. Removes parsed
1110
* part from \a str and updates \a dur with the findings.
1112
void Duration::parseDurationSeconds(QString* str, Duration* dur)
1114
QString token = nextToken(str);
1115
if (token.isEmpty() || !token.at(0).isDigit())
1116
dur->setValid(false);
1118
int value = token.toInt(); // always succeeds
1119
token = nextToken(str);
1120
if (token != QStringLiteral("S")) {
1121
dur->setValid(false);
1124
dur->setSeconds(value);
1127
/*! Removes and returns a "token" from the start of an iCalendar DURATION string, \a str. A token
1128
* is either a single +, - or upper-case letter, or a string of digits. If \a str is empty, an
1129
* empty string is returned. If \a str is not empty but starts with an invalid character, a null
1130
* string is returned.
1132
QString Duration::nextToken(QString* str)
1134
int len = str->length();
1136
return QString::fromLatin1(""); // empty (not null) QString
1137
QChar first = str->at(0);
1138
if (first == QLatin1Char('+') || first == QLatin1Char('-') || first.isUpper()) {
1139
QString ret(str->left(1));
1142
} else if (first.isDigit()) {
1143
// find the largest n such that the leftmost n characters are digits
1145
for (n = 1; n < len && str->at(n).isDigit(); n++) {
1147
QString ret = str->left(n);
1151
return QString(); // null QString
1155
TimeZone QVersitOrganizerImporterPrivate::importTimeZone(const QVersitDocument& document) const
1158
foreach (const QVersitProperty& property, document.properties()) {
1159
if (property.name() == QStringLiteral("TZID") && !property.value().isEmpty()) {
1160
timeZone.setTzid(property.value());
1163
foreach (const QVersitDocument& subDocument, document.subDocuments()) {
1164
timeZone.addPhase(importTimeZonePhase(subDocument));
1169
TimeZonePhase QVersitOrganizerImporterPrivate::importTimeZonePhase(const QVersitDocument& document) const
1171
TimeZonePhase phase;
1172
phase.setStandard(document.componentType() == QStringLiteral("STANDARD"));
1174
foreach (const QVersitProperty& property, document.properties()) {
1175
if (property.name() == QStringLiteral("TZOFFSETTO")) {
1176
QString value(property.value());
1177
if (value.size() == 5
1178
&& (value.at(0) == QLatin1Char('+') || value.at(0) == QLatin1Char('-'))
1179
&& value.at(1).isDigit()
1180
&& value.at(2).isDigit()
1181
&& value.at(3).isDigit()
1182
&& value.at(4).isDigit()) {
1183
phase.setUtcOffset((value.at(0) == QLatin1Char('+') ? 1 : -1) * ( // deal with sign
1184
value.mid(1, 2).toInt() * 3600 // hours part
1185
+ value.mid(3, 2).toInt() * 60 // minutes part
1188
} else if (property.name() == QStringLiteral("DTSTART")) {
1189
QDateTime dt(parseDateTime(property.value()));
1190
if (dt.isValid() && dt.timeSpec() == Qt::LocalTime) {
1191
phase.setStartDateTime(dt);
1193
} else if (property.name() == QStringLiteral("RRULE")) {
1194
QOrganizerRecurrenceRule rrule;
1195
if (parseRecurRule(property.value(), &rrule)) {
1196
phase.setRecurrenceRule(rrule);
1198
} else if (property.name() == QStringLiteral("RDATE")) {
1200
if (parseDates(property.value(), &dates)) {
1201
phase.setRecurrenceDates(dates);
1208
QT_END_NAMESPACE_VERSITORGANIZER