qtmobility/src/versit/qversitreader_p.cpp
changeset 4 90517678cc4f
parent 1 2b40d63a9c3d
child 5 453da2cfceef
--- a/qtmobility/src/versit/qversitreader_p.cpp	Fri Apr 16 15:51:22 2010 +0300
+++ b/qtmobility/src/versit/qversitreader_p.cpp	Mon May 03 13:18:40 2010 +0300
@@ -56,6 +56,7 @@
 /*!
   \class LineReader
   \brief The LineReader class is a wrapper around a QIODevice that allows line-by-line reading.
+  \internal
 
   This class keeps an internal buffer which it uses to temporarily store data which it has read from
   the device but not returned to the user.
@@ -150,7 +151,7 @@
  */
 bool LineReader::tryReadLine(VersitCursor &cursor, bool atEnd)
 {
-    int crlfPos;
+    int crlfPos = -1;
 
     QByteArray space = VersitUtils::encode(' ', mCodec);
     QByteArray tab = VersitUtils::encode('\t', mCodec);
@@ -194,6 +195,16 @@
     }
 }
 
+/*! Links the signals from this to the signals of \a reader. */
+void QVersitReaderPrivate::init(QVersitReader* reader)
+{
+    qRegisterMetaType<QVersitReader::State>("QVersitReader::State");
+    connect(this, SIGNAL(stateChanged(QVersitReader::State)),
+            reader, SIGNAL(stateChanged(QVersitReader::State)),Qt::DirectConnection);
+    connect(this, SIGNAL(resultsAvailable()),
+            reader, SIGNAL(resultsAvailable()), Qt::DirectConnection);
+}
+
 /*! Construct a reader. */
 QVersitReaderPrivate::QVersitReaderPrivate()
     : mIoDevice(0),
@@ -203,6 +214,34 @@
     mError(QVersitReader::NoError),
     mIsCanceling(false)
 {
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("AGENT")),
+                         QVersitProperty::VersitDocumentType);
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("AGENT")),
+                         QVersitProperty::VersitDocumentType);
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("N")),
+                         QVersitProperty::CompoundType);
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("N")),
+                         QVersitProperty::CompoundType);
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("ADR")),
+                         QVersitProperty::CompoundType);
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("ADR")),
+                         QVersitProperty::CompoundType);
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("GEO")),
+                         QVersitProperty::CompoundType);
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("GEO")),
+                         QVersitProperty::CompoundType);
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("ORG")),
+                         QVersitProperty::CompoundType);
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("ORG")),
+                         QVersitProperty::CompoundType);
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("NICKNAMES")),
+                         QVersitProperty::ListType);
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("NICKNAMES")),
+                         QVersitProperty::ListType);
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("CATEGORIES")),
+                         QVersitProperty::ListType);
+    mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("CATEGORIES")),
+                         QVersitProperty::ListType);
 }
 
 /*! Destroy a reader. */
@@ -245,7 +284,7 @@
             else {
                 QMutexLocker locker(&mMutex);
                 mVersitDocuments.append(document);
-                emit resultsAvailable(mVersitDocuments);
+                emit resultsAvailable();
             }
         } else {
             setError(QVersitReader::ParseError);
@@ -401,9 +440,9 @@
     property.setParameters(extractVCard21PropertyParams(cursor, lineReader.codec()));
 
     QByteArray value = extractPropertyValue(cursor);
-    if (property.name() == QLatin1String("AGENT")) {
+    if (mValueTypeMap.value(qMakePair(QVersitDocument::VCard21Type, property.name()))
+            == QVersitProperty::VersitDocumentType) {
         // Hack to handle cases where start of document is on the same or next line as "AGENT:"
-        // XXX: Handle non-ASCII charsets in nested AGENT documents.
         bool foundBegin = false;
         if (value == "BEGIN:VCARD") {
             foundBegin = true;
@@ -412,17 +451,20 @@
             property = QVersitProperty();
             return;
         }
-        QVersitDocument agentDocument;
-        if (!parseVersitDocument(lineReader, agentDocument, foundBegin)) {
+        QVersitDocument subDocument;
+        if (!parseVersitDocument(lineReader, subDocument, foundBegin)) {
             property = QVersitProperty();
         } else {
-            property.setValue(QVariant::fromValue(agentDocument));
+            property.setValue(QVariant::fromValue(subDocument));
         }
     } else {
         QTextCodec* codec;
         QVariant valueVariant(decodeCharset(value, property, lineReader.codec(), &codec));
-        unencode(valueVariant, cursor, property, codec, lineReader);
+        bool isBinary = unencode(valueVariant, cursor, property, codec, lineReader);
         property.setValue(valueVariant);
+        if (!isBinary) {
+            splitStructuredValue(QVersitDocument::VCard21Type, property, false);
+        }
     }
 }
 
@@ -438,32 +480,42 @@
 
     QTextCodec* codec;
     QString valueString(decodeCharset(value, property, lineReader.codec(), &codec));
-    VersitUtils::removeBackSlashEscaping(valueString);
 
-    if (property.name() == QLatin1String("AGENT")) {
+    if (mValueTypeMap.value(qMakePair(QVersitDocument::VCard30Type, property.name()))
+            == QVersitProperty::VersitDocumentType) {
+        removeBackSlashEscaping(valueString);
         // Make a line reader from the value of the property.
-        QByteArray agentValue(codec->fromUnicode(valueString));
-        QBuffer agentData(&agentValue);
-        agentData.open(QIODevice::ReadOnly);
-        agentData.seek(0);
-        LineReader agentLineReader(&agentData, codec);
+        QByteArray subDocumentValue(codec->fromUnicode(valueString));
+        QBuffer subDocumentData(&subDocumentValue);
+        subDocumentData.open(QIODevice::ReadOnly);
+        subDocumentData.seek(0);
+        LineReader subDocumentLineReader(&subDocumentData, codec);
 
-        QVersitDocument agentDocument;
-        if (!parseVersitDocument(agentLineReader, agentDocument)) {
+        QVersitDocument subDocument;
+        if (!parseVersitDocument(subDocumentLineReader, subDocument)) {
             property = QVersitProperty();
         } else {
-            property.setValue(QVariant::fromValue(agentDocument));
+            property.setValue(QVariant::fromValue(subDocument));
         }
     } else {
         QVariant valueVariant(valueString);
-        unencode(valueVariant, cursor, property, codec, lineReader);
-        if (valueVariant.type() == QVariant::ByteArray) {
-            // hack: add the charset parameter back in (even if there wasn't one to start with and
-            // the default codec was used).  This will help later on if someone calls valueString()
-            // on the property.
-            property.insertParameter(QLatin1String("CHARSET"), QLatin1String(codec->name()));
+        bool isBinary = unencode(valueVariant, cursor, property, codec, lineReader);
+        property.setValue(valueVariant);
+        if (!isBinary) {
+            bool isList = splitStructuredValue(QVersitDocument::VCard30Type, property, true);
+            // Do backslash unescaping
+            if (isList) {
+                QStringList list = property.value<QStringList>();
+                for (int i = 0; i < list.length(); i++) {
+                    removeBackSlashEscaping(list[i]);
+                }
+                property.setValue(list);
+            } else {
+                QString value = property.value();
+                removeBackSlashEscaping(value);
+                property.setValue(value);
+            }
         }
-        property.setValue(valueVariant);
     }
 }
 
@@ -491,8 +543,9 @@
 
 /*!
  * On entry, \a value should hold a QString.  On exit, it may be either a QString or a QByteArray.
+ * Returns true if and only if the property value is turned into a QByteArray.
  */
-void QVersitReaderPrivate::unencode(QVariant& value, VersitCursor& cursor,
+bool QVersitReaderPrivate::unencode(QVariant& value, VersitCursor& cursor,
                                     QVersitProperty& property, QTextCodec* codec,
                                     LineReader& lineReader) const
 {
@@ -515,6 +568,7 @@
         // Remove the encoding parameter as the value is now decoded
         property.removeParameters(QLatin1String("ENCODING"));
         value.setValue(valueString);
+        return false;
     } else if (property.parameters().contains(QLatin1String("ENCODING"), QLatin1String("BASE64"))
         || property.parameters().contains(QLatin1String("ENCODING"), QLatin1String("B"))
         || property.parameters().contains(QLatin1String("TYPE"), QLatin1String("BASE64"))
@@ -526,7 +580,9 @@
         // the default codec was used).  This will help later on if someone calls valueString()
         // on the property.
         property.insertParameter(QLatin1String("CHARSET"), QLatin1String(codec->name()));
+        return true;
     }
+    return false;
 }
 
 /*!
@@ -582,7 +638,6 @@
     }
 }
 
-
 /*!
  * Extracts the groups and the name of the property using \a codec to determine the delimiters
  *
@@ -674,25 +729,14 @@
     while (!paramList.isEmpty()) {
         QByteArray param = paramList.takeLast();
         QString name(paramName(param, codec));
-        VersitUtils::removeBackSlashEscaping(name);
+        removeBackSlashEscaping(name);
         QString values = paramValue(param, codec);
-        QList<QString> valueList = values.split(QLatin1Char(','), QString::SkipEmptyParts);
-        QString buffer; // for any part ending in a backslash, join it to the next.
+        QStringList valueList = splitValue(values, QLatin1Char(','), QString::SkipEmptyParts, true);
         foreach (QString value, valueList) {
-            if (value.endsWith(QLatin1Char('\\')) && !value.endsWith(QLatin1String("\\\\"))) {
-                value.chop(1);
-                buffer.append(value);
-                buffer.append(QLatin1Char(',')); // because the comma got nuked by split()
-            }
-            else {
-                buffer.append(value);
-                VersitUtils::removeBackSlashEscaping(buffer);
-                result.insert(name, buffer);
-                buffer.clear();
-            }
+            removeBackSlashEscaping(value);
+            result.insert(name, value);
         }
     }
-
     return result;
 }
 
@@ -817,4 +861,100 @@
     return memcmp(textData+index, matchData, n) == 0;
 }
 
+/*!
+ * If the \a type and the \a property's name is known to contain a structured value, \a property's
+ * value is split according to the type of structuring (compound vs. list) it is known to have.
+ * Returns true if and only if such a split happened (ie. the property value holds a QStringList on
+ * exit).
+ */
+bool QVersitReaderPrivate::splitStructuredValue(
+        QVersitDocument::VersitType type, QVersitProperty& property,
+        bool hasEscapedBackslashes) const
+{
+    QVariant variant = property.variantValue();
+    QPair<QVersitDocument::VersitType,QString> key = qMakePair(type, property.name());
+    if (mValueTypeMap.contains(key)) {
+        if (mValueTypeMap.value(key) == QVersitProperty::CompoundType) {
+            variant.setValue(splitValue(variant.toString(), QLatin1Char(';'),
+                                        QString::KeepEmptyParts, hasEscapedBackslashes));
+            property.setValue(variant);
+            property.setValueType(QVersitProperty::CompoundType);
+        } else if (mValueTypeMap.value(key) == QVersitProperty::ListType) {
+            variant.setValue(splitValue(variant.toString(), QLatin1Char(','),
+                                        QString::SkipEmptyParts, hasEscapedBackslashes));
+            property.setValue(variant);
+            property.setValueType(QVersitProperty::ListType);
+        }
+        return true;
+    }
+    return false;
+}
+
+/*!
+ * Splits the \a string into substrings wherever \a sep occurs.
+ * If \a hasEscapedBackslashes is false, then a \a sep preceded by a backslash is not considered
+ * a split point (but the backslash is removed).
+ * If \a hasEscapedBackslashes is true, then a \a sep preceded by an odd number of backslashes is
+ * not considered a split point (but one backslash is removed).
+ */
+QStringList QVersitReaderPrivate::splitValue(const QString& string,
+                                             const QChar& sep,
+                                             QString::SplitBehavior behaviour,
+                                             bool hasEscapedBackslashes)
+{
+    QStringList list;
+    bool isEscaped = false; // is the current character escaped
+    int segmentStartIndex = 0;
+    QString segment;
+    for (int i = 0; i < string.length(); i++) {
+        if (string.at(i) == QLatin1Char('\\')) {
+            if (hasEscapedBackslashes)
+                isEscaped = !isEscaped; // two consecutive backslashes make isEscaped false
+            else
+                isEscaped = true;
+        } else if (string.at(i) == sep) {
+            if (isEscaped) {
+                // we see an escaped separator - remove the backslash
+                segment += string.midRef(segmentStartIndex, i-segmentStartIndex-1);
+                segment += sep;
+            } else {
+                // we see a separator
+                segment += string.midRef(segmentStartIndex, i - segmentStartIndex);
+                if (behaviour == QString::KeepEmptyParts || !segment.isEmpty())
+                    list.append(segment);
+                segment.clear();
+            }
+            segmentStartIndex = i+1;
+            isEscaped = false;
+        } else { // normal character - keep going
+            isEscaped = false;
+        }
+    }
+    // The rest of the string after the last sep.
+    segment += string.midRef(segmentStartIndex);
+    if (behaviour == QString::KeepEmptyParts || !segment.isEmpty())
+        list.append(segment);
+    return list;
+}
+
+/*!
+ * Removes backslash escaping for line breaks (CRLFs), colons, semicolons, backslashes and commas
+ * according to RFC 2426.  This is called on parameter names and values and property values.
+ * Colons ARE unescaped because the text of RFC2426 suggests that they should be.
+ */
+void QVersitReaderPrivate::removeBackSlashEscaping(QString& text)
+{
+    if (!(text.startsWith(QLatin1Char('"')) && text.endsWith(QLatin1Char('"')))) {
+        /* replaces \; with ;
+                    \, with ,
+                    \: with :
+                    \\ with \
+         */
+        text.replace(QRegExp(QLatin1String("\\\\([;,:\\\\])")), QLatin1String("\\1"));
+        // replaces \n with a CRLF
+        text.replace(QLatin1String("\\n"), QLatin1String("\r\n"), Qt::CaseInsensitive);
+    }
+}
+
+
 #include "moc_qversitreader_p.cpp"