qtmobility/src/versit/qversitreader_p.cpp
changeset 1 2b40d63a9c3d
child 4 90517678cc4f
equal deleted inserted replaced
0:cfcbf08528c4 1:2b40d63a9c3d
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the Qt Mobility Components.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "qversitreader_p.h"
       
    43 #include "qversitdocument.h"
       
    44 #include "versitutils_p.h"
       
    45 #include "qmobilityglobal.h"
       
    46 #include <QTextCodec>
       
    47 #include <QMutexLocker>
       
    48 #include <QVariant>
       
    49 #include <QBuffer>
       
    50 
       
    51 QTM_USE_NAMESPACE
       
    52 
       
    53 // Some big enough value for nested versit documents to prevent infinite recursion
       
    54 #define MAX_VERSIT_DOCUMENT_NESTING_DEPTH 20
       
    55 
       
    56 /*!
       
    57   \class LineReader
       
    58   \brief The LineReader class is a wrapper around a QIODevice that allows line-by-line reading.
       
    59 
       
    60   This class keeps an internal buffer which it uses to temporarily store data which it has read from
       
    61   the device but not returned to the user.
       
    62  */
       
    63 
       
    64 /*!
       
    65   Constructs a LineReader that reads from the given \a device using the given \a codec.
       
    66   \a chunkSize is the number of bytes to read at a time (it is useful for testing but shouldn't
       
    67   otherwise be set).
       
    68   */
       
    69 LineReader::LineReader(QIODevice* device, QTextCodec *codec, int chunkSize)
       
    70     : mDevice(device),
       
    71     mCodec(codec),
       
    72     mChunkSize(chunkSize),
       
    73     mCrlfList(*VersitUtils::newlineList(mCodec)),
       
    74     mBuffer(VersitCursor(QByteArray())),
       
    75     mOdometer(0)
       
    76 {
       
    77 }
       
    78 
       
    79 /*!
       
    80   Attempts to read a line and returns a VersitCursor describing the line.  The cursor returned
       
    81   includes the data, as well as the position and selection index bounds.  Data within those bounds
       
    82   represents the line.  Data outside those bounds should not be used.
       
    83  */
       
    84 VersitCursor LineReader::readLine()
       
    85 {
       
    86     mBuffer.position = mBuffer.selection;
       
    87     mSearchFrom = mBuffer.position;
       
    88 
       
    89     // First, look for a newline in the already-existing buffer.  If found, return the line.
       
    90     if (tryReadLine(mBuffer, false)) {
       
    91         mBuffer.dropOldData();
       
    92         mOdometer += mBuffer.selection - mBuffer.position;
       
    93         return mBuffer;
       
    94     }
       
    95 
       
    96     // Otherwise, keep reading more data until either a CRLF is found, or there's no more to read.
       
    97     while (!mDevice->atEnd()) {
       
    98         QByteArray temp = mDevice->read(mChunkSize);
       
    99         if (!temp.isEmpty()) {
       
   100             mBuffer.data.append(temp);
       
   101             if (tryReadLine(mBuffer, false)) {
       
   102                 mBuffer.dropOldData();
       
   103                 mOdometer += mBuffer.selection - mBuffer.position;
       
   104                 return mBuffer;
       
   105             }
       
   106         } else {
       
   107             mDevice->waitForReadyRead(500);
       
   108         }
       
   109     }
       
   110 
       
   111     // We've reached the end of the stream.  Find a newline from the buffer (or return what's left).
       
   112     tryReadLine(mBuffer, true);
       
   113     mBuffer.dropOldData();
       
   114     mOdometer += mBuffer.selection - mBuffer.position;
       
   115     return mBuffer;
       
   116 }
       
   117 
       
   118 /*!
       
   119   How many bytes have been returned in the VersitCursor in the lifetime of the LineReader.
       
   120  */
       
   121 int LineReader::odometer()
       
   122 {
       
   123     return mOdometer;
       
   124 }
       
   125 
       
   126 /*!
       
   127   Returns true if there are no more lines left for readLine() to return.  It is possible for atEnd()
       
   128   to return false and for there to be no more data left (eg. if there are trailing newlines at the
       
   129   end of the input.  In this case, readLine() will return an empty line (ie. position == selection).
       
   130  */
       
   131 bool LineReader::atEnd()
       
   132 {
       
   133     return mDevice->atEnd() && mBuffer.selection == mBuffer.data.size();
       
   134 }
       
   135 
       
   136 /*!
       
   137   Returns the codec that the LineReader reads with.
       
   138  */
       
   139 QTextCodec* LineReader::codec()
       
   140 {
       
   141     return mCodec;
       
   142 }
       
   143 
       
   144 /*!
       
   145  * Get the next line of input from \a device to parse.  Also performs unfolding by removing
       
   146  * sequences of newline-space from the retrieved line.  Skips over any newlines at the start of the
       
   147  * input.
       
   148  *
       
   149  * Returns a VersitCursor containing and selecting the line.
       
   150  */
       
   151 bool LineReader::tryReadLine(VersitCursor &cursor, bool atEnd)
       
   152 {
       
   153     int crlfPos;
       
   154 
       
   155     QByteArray space = VersitUtils::encode(' ', mCodec);
       
   156     QByteArray tab = VersitUtils::encode('\t', mCodec);
       
   157     int spaceLength = space.length();
       
   158 
       
   159     forever {
       
   160         foreach(const QByteArrayMatcher& crlf, mCrlfList) {
       
   161             int crlfLength = crlf.pattern().length();
       
   162             crlfPos = crlf.indexIn(cursor.data, mSearchFrom);
       
   163             if (crlfPos == cursor.position) {
       
   164                 // Newline at start of line.  Set position to directly after it.
       
   165                 cursor.position += crlfLength;
       
   166                 mSearchFrom = cursor.position;
       
   167                 break;
       
   168             } else if (crlfPos > cursor.position) {
       
   169                 // Found the CRLF.
       
   170                 if (QVersitReaderPrivate::containsAt(cursor.data, space, crlfPos + crlfLength)
       
   171                     || QVersitReaderPrivate::containsAt(cursor.data, tab, crlfPos + crlfLength)) {
       
   172                     // If it's followed by whitespace, collapse it.
       
   173                     cursor.data.remove(crlfPos, crlfLength + spaceLength);
       
   174                     mSearchFrom = crlfPos;
       
   175                     break;
       
   176                 } else if (!atEnd && crlfPos + crlfLength + spaceLength >= cursor.data.size()) {
       
   177                     // If our CRLF is at the end of the current buffer but there's more to read,
       
   178                     // it's possible that a space could be hiding on the next read from the device.
       
   179                     // Just pretend we didn't see the CRLF and pick it up the next time round.
       
   180                     mSearchFrom = crlfPos;
       
   181                     return false;
       
   182                 } else {
       
   183                     // Found the CRLF.
       
   184                     cursor.selection = crlfPos;
       
   185                     return true;
       
   186                 }
       
   187             }
       
   188         }
       
   189         if (crlfPos == -1) {
       
   190             // No CRLF found.
       
   191             cursor.selection = cursor.data.size();
       
   192             return false;
       
   193         }
       
   194     }
       
   195 }
       
   196 
       
   197 /*! Construct a reader. */
       
   198 QVersitReaderPrivate::QVersitReaderPrivate()
       
   199     : mIoDevice(0),
       
   200     mDocumentNestingLevel(0),
       
   201     mDefaultCodec(QTextCodec::codecForName("UTF-8")),
       
   202     mState(QVersitReader::InactiveState),
       
   203     mError(QVersitReader::NoError),
       
   204     mIsCanceling(false)
       
   205 {
       
   206 }
       
   207 
       
   208 /*! Destroy a reader. */
       
   209 QVersitReaderPrivate::~QVersitReaderPrivate()
       
   210 {
       
   211 }
       
   212 
       
   213 /*!
       
   214  * Inherited from QThread, called by QThread when the thread has been started.
       
   215  */
       
   216 void QVersitReaderPrivate::run()
       
   217 {
       
   218     read();
       
   219 }
       
   220 
       
   221 /*!
       
   222  * Does the actual reading and sets the error and state as appropriate.
       
   223  * If \a async, then stateChanged() signals are emitted as the reading happens.
       
   224  */
       
   225 void QVersitReaderPrivate::read()
       
   226 {
       
   227     mMutex.lock();
       
   228     mVersitDocuments.clear();
       
   229     mMutex.unlock();
       
   230     bool canceled = false;
       
   231 
       
   232     LineReader lineReader(mIoDevice, mDefaultCodec);
       
   233     while(!lineReader.atEnd()) {
       
   234         if (isCanceling()) {
       
   235             canceled = true;
       
   236             break;
       
   237         }
       
   238         QVersitDocument document;
       
   239         int oldPos = lineReader.odometer();
       
   240         bool ok = parseVersitDocument(lineReader, document);
       
   241 
       
   242         if (ok) {
       
   243             if (document.isEmpty())
       
   244                 break;
       
   245             else {
       
   246                 QMutexLocker locker(&mMutex);
       
   247                 mVersitDocuments.append(document);
       
   248                 emit resultsAvailable(mVersitDocuments);
       
   249             }
       
   250         } else {
       
   251             setError(QVersitReader::ParseError);
       
   252             if (lineReader.odometer() == oldPos)
       
   253                 break;
       
   254         }
       
   255     };
       
   256     if (canceled)
       
   257         setState(QVersitReader::CanceledState);
       
   258     else
       
   259         setState(QVersitReader::FinishedState);
       
   260 }
       
   261 
       
   262 void QVersitReaderPrivate::setState(QVersitReader::State state)
       
   263 {
       
   264     mMutex.lock();
       
   265     mState = state;
       
   266     mMutex.unlock();
       
   267     emit stateChanged(state);
       
   268 }
       
   269 
       
   270 QVersitReader::State QVersitReaderPrivate::state() const
       
   271 {
       
   272     QMutexLocker locker(&mMutex);
       
   273     return mState;
       
   274 }
       
   275 
       
   276 void QVersitReaderPrivate::setError(QVersitReader::Error error)
       
   277 {
       
   278     QMutexLocker locker(&mMutex);
       
   279     mError = error;
       
   280 }
       
   281 
       
   282 QVersitReader::Error QVersitReaderPrivate::error() const
       
   283 {
       
   284     QMutexLocker locker(&mMutex);
       
   285     return mError;
       
   286 }
       
   287 
       
   288 void QVersitReaderPrivate::setCanceling(bool canceling)
       
   289 {
       
   290     QMutexLocker locker(&mMutex);
       
   291     mIsCanceling = canceling;
       
   292 }
       
   293 
       
   294 bool QVersitReaderPrivate::isCanceling()
       
   295 {
       
   296     QMutexLocker locker(&mMutex);
       
   297     return mIsCanceling;
       
   298 }
       
   299 
       
   300 /*!
       
   301  * Parses a versit document. Returns true if the parsing was successful.
       
   302  */
       
   303 bool QVersitReaderPrivate::parseVersitDocument(LineReader& lineReader, QVersitDocument& document,
       
   304                                                bool foundBegin)
       
   305 {
       
   306     if (mDocumentNestingLevel >= MAX_VERSIT_DOCUMENT_NESTING_DEPTH)
       
   307         return false; // To prevent infinite recursion
       
   308 
       
   309     bool parsingOk = true;
       
   310     mDocumentNestingLevel++;
       
   311 
       
   312     // TODO: Various readers should be made subclasses and eliminate assumptions like this.
       
   313     // We don't know what type it is: just assume it's a vCard 3.0
       
   314     document.setType(QVersitDocument::VCard30Type);
       
   315 
       
   316     QVersitProperty property;
       
   317 
       
   318     if (!foundBegin) {
       
   319         property = parseNextVersitProperty(document.type(), lineReader);
       
   320         if (property.name() == QLatin1String("BEGIN")
       
   321             && property.value().trimmed().toUpper() == QLatin1String("VCARD")) {
       
   322             foundBegin = true;
       
   323         } else if (property.isEmpty()) {
       
   324             // A blank document (or end of file) was found.
       
   325             document = QVersitDocument();
       
   326         } else {
       
   327             // Some property other than BEGIN was found.
       
   328             parsingOk = false;
       
   329         }
       
   330     }
       
   331 
       
   332     if (foundBegin) {
       
   333         do {
       
   334             /* Grab it */
       
   335             property = parseNextVersitProperty(document.type(), lineReader);
       
   336 
       
   337             /* Discard embedded vcard documents - not supported yet.  Discard the entire vCard */
       
   338             if (property.name() == QLatin1String("BEGIN") &&
       
   339                 QString::compare(property.value().trimmed(),
       
   340                                  QLatin1String("VCARD"), Qt::CaseInsensitive) == 0) {
       
   341                 parsingOk = false;
       
   342                 QVersitDocument nestedDocument;
       
   343                 if (!parseVersitDocument(lineReader, nestedDocument, true))
       
   344                     break;
       
   345             }
       
   346 
       
   347             // See if this is a version property and continue parsing under that version
       
   348             if (!setVersionFromProperty(document, property)) {
       
   349                 parsingOk = false;
       
   350                 break;
       
   351             }
       
   352 
       
   353             /* Nope, something else.. just add it */
       
   354             if (property.name() != QLatin1String("VERSION") &&
       
   355                 property.name() != QLatin1String("END"))
       
   356                 document.addProperty(property);
       
   357         } while (property.name().length() > 0 && property.name() != QLatin1String("END"));
       
   358         if (property.name() != QLatin1String("END"))
       
   359             parsingOk = false;
       
   360     }
       
   361     mDocumentNestingLevel--;
       
   362     if (!parsingOk)
       
   363         document = QVersitDocument();
       
   364 
       
   365     return parsingOk;
       
   366 }
       
   367 
       
   368 /*!
       
   369  * Parses a versit document and returns whether parsing succeeded.
       
   370  */
       
   371 QVersitProperty QVersitReaderPrivate::parseNextVersitProperty(
       
   372         QVersitDocument::VersitType versitType,
       
   373         LineReader& lineReader)
       
   374 {
       
   375     VersitCursor cursor = lineReader.readLine();
       
   376     if (cursor.position >= cursor.selection)
       
   377         return QVersitProperty();
       
   378 
       
   379     // Otherwise, do stuff.
       
   380     QPair<QStringList,QString> groupsAndName =
       
   381             extractPropertyGroupsAndName(cursor, lineReader.codec());
       
   382 
       
   383     QVersitProperty property;
       
   384     property.setGroups(groupsAndName.first);
       
   385     property.setName(groupsAndName.second);
       
   386 
       
   387     if (versitType == QVersitDocument::VCard21Type)
       
   388         parseVCard21Property(cursor, property, lineReader);
       
   389     else if (versitType == QVersitDocument::VCard30Type)
       
   390         parseVCard30Property(cursor, property, lineReader);
       
   391 
       
   392     return property;
       
   393 }
       
   394 
       
   395 /*!
       
   396  * Parses the property according to vCard 2.1 syntax.
       
   397  */
       
   398 void QVersitReaderPrivate::parseVCard21Property(VersitCursor& cursor, QVersitProperty& property,
       
   399                                                 LineReader& lineReader)
       
   400 {
       
   401     property.setParameters(extractVCard21PropertyParams(cursor, lineReader.codec()));
       
   402 
       
   403     QByteArray value = extractPropertyValue(cursor);
       
   404     if (property.name() == QLatin1String("AGENT")) {
       
   405         // Hack to handle cases where start of document is on the same or next line as "AGENT:"
       
   406         // XXX: Handle non-ASCII charsets in nested AGENT documents.
       
   407         bool foundBegin = false;
       
   408         if (value == "BEGIN:VCARD") {
       
   409             foundBegin = true;
       
   410         } else if (value.isEmpty()) {
       
   411         } else {
       
   412             property = QVersitProperty();
       
   413             return;
       
   414         }
       
   415         QVersitDocument agentDocument;
       
   416         if (!parseVersitDocument(lineReader, agentDocument, foundBegin)) {
       
   417             property = QVersitProperty();
       
   418         } else {
       
   419             property.setValue(QVariant::fromValue(agentDocument));
       
   420         }
       
   421     } else {
       
   422         QTextCodec* codec;
       
   423         QVariant valueVariant(decodeCharset(value, property, lineReader.codec(), &codec));
       
   424         unencode(valueVariant, cursor, property, codec, lineReader);
       
   425         property.setValue(valueVariant);
       
   426     }
       
   427 }
       
   428 
       
   429 /*!
       
   430  * Parses the property according to vCard 3.0 syntax.
       
   431  */
       
   432 void QVersitReaderPrivate::parseVCard30Property(VersitCursor& cursor, QVersitProperty& property,
       
   433                                                 LineReader& lineReader)
       
   434 {
       
   435     property.setParameters(extractVCard30PropertyParams(cursor, lineReader.codec()));
       
   436 
       
   437     QByteArray value = extractPropertyValue(cursor);
       
   438 
       
   439     QTextCodec* codec;
       
   440     QString valueString(decodeCharset(value, property, lineReader.codec(), &codec));
       
   441     VersitUtils::removeBackSlashEscaping(valueString);
       
   442 
       
   443     if (property.name() == QLatin1String("AGENT")) {
       
   444         // Make a line reader from the value of the property.
       
   445         QByteArray agentValue(codec->fromUnicode(valueString));
       
   446         QBuffer agentData(&agentValue);
       
   447         agentData.open(QIODevice::ReadOnly);
       
   448         agentData.seek(0);
       
   449         LineReader agentLineReader(&agentData, codec);
       
   450 
       
   451         QVersitDocument agentDocument;
       
   452         if (!parseVersitDocument(agentLineReader, agentDocument)) {
       
   453             property = QVersitProperty();
       
   454         } else {
       
   455             property.setValue(QVariant::fromValue(agentDocument));
       
   456         }
       
   457     } else {
       
   458         QVariant valueVariant(valueString);
       
   459         unencode(valueVariant, cursor, property, codec, lineReader);
       
   460         if (valueVariant.type() == QVariant::ByteArray) {
       
   461             // hack: add the charset parameter back in (even if there wasn't one to start with and
       
   462             // the default codec was used).  This will help later on if someone calls valueString()
       
   463             // on the property.
       
   464             property.insertParameter(QLatin1String("CHARSET"), QLatin1String(codec->name()));
       
   465         }
       
   466         property.setValue(valueVariant);
       
   467     }
       
   468 }
       
   469 
       
   470 /*!
       
   471  * Sets version to \a document if \a property contains a supported version.
       
   472  */
       
   473 bool QVersitReaderPrivate::setVersionFromProperty(QVersitDocument& document, const QVersitProperty& property) const
       
   474 {
       
   475     bool valid = true;
       
   476     if (property.name() == QLatin1String("VERSION")) {
       
   477         QString value = property.value().trimmed();
       
   478         if (property.parameters().contains(QLatin1String("ENCODING"),QLatin1String("BASE64"))
       
   479             || property.parameters().contains(QLatin1String("TYPE"),QLatin1String("BASE64")))
       
   480             value = QLatin1String(QByteArray::fromBase64(value.toAscii()));
       
   481         if (value == QLatin1String("2.1")) {
       
   482             document.setType(QVersitDocument::VCard21Type);
       
   483         } else if (value == QLatin1String("3.0")) {
       
   484             document.setType(QVersitDocument::VCard30Type);
       
   485         } else {
       
   486             valid = false;
       
   487         }
       
   488     }
       
   489     return valid;
       
   490 }
       
   491 
       
   492 /*!
       
   493  * On entry, \a value should hold a QString.  On exit, it may be either a QString or a QByteArray.
       
   494  */
       
   495 void QVersitReaderPrivate::unencode(QVariant& value, VersitCursor& cursor,
       
   496                                     QVersitProperty& property, QTextCodec* codec,
       
   497                                     LineReader& lineReader) const
       
   498 {
       
   499     Q_ASSERT(value.type() == QVariant::String);
       
   500 
       
   501     QString valueString = value.toString();
       
   502 
       
   503     if (property.parameters().contains(QLatin1String("ENCODING"), QLatin1String("QUOTED-PRINTABLE"))) {
       
   504         // At this point, we need to accumulate bytes until we hit a real line break (no = before
       
   505         // it) value already contains everything up to the character before the newline
       
   506         while (valueString.endsWith(QLatin1Char('='))) {
       
   507             valueString.chop(1); // Get rid of '='
       
   508             // We add each line (minus the escaped = and newline chars)
       
   509             cursor = lineReader.readLine();
       
   510             QString line = codec->toUnicode(
       
   511                     cursor.data.mid(cursor.position, cursor.selection-cursor.position));
       
   512             valueString.append(line);
       
   513         }
       
   514         decodeQuotedPrintable(valueString);
       
   515         // Remove the encoding parameter as the value is now decoded
       
   516         property.removeParameters(QLatin1String("ENCODING"));
       
   517         value.setValue(valueString);
       
   518     } else if (property.parameters().contains(QLatin1String("ENCODING"), QLatin1String("BASE64"))
       
   519         || property.parameters().contains(QLatin1String("ENCODING"), QLatin1String("B"))
       
   520         || property.parameters().contains(QLatin1String("TYPE"), QLatin1String("BASE64"))
       
   521         || property.parameters().contains(QLatin1String("TYPE"), QLatin1String("B"))) {
       
   522         value.setValue(QByteArray::fromBase64(valueString.toAscii()));
       
   523         // Remove the encoding parameter as the value is now decoded
       
   524         property.removeParameters(QLatin1String("ENCODING"));
       
   525         // Hack: add the charset parameter back in (even if there wasn't one to start with and
       
   526         // the default codec was used).  This will help later on if someone calls valueString()
       
   527         // on the property.
       
   528         property.insertParameter(QLatin1String("CHARSET"), QLatin1String(codec->name()));
       
   529     }
       
   530 }
       
   531 
       
   532 /*!
       
   533  * Decodes \a value, after working out what charset it is in using the context of \a property and
       
   534  * returns it.  The codec used to decode is returned in \a codec.
       
   535  */
       
   536 QString QVersitReaderPrivate::decodeCharset(const QByteArray& value,
       
   537                                             QVersitProperty& property,
       
   538                                             QTextCodec* defaultCodec,
       
   539                                             QTextCodec** codec) const
       
   540 {
       
   541     const QString charset(QLatin1String("CHARSET"));
       
   542     if (property.parameters().contains(charset)) {
       
   543         QString charsetValue = *property.parameters().find(charset);
       
   544         property.removeParameters(charset);
       
   545         *codec = QTextCodec::codecForName(charsetValue.toAscii());
       
   546         if (*codec != NULL) {
       
   547             return (*codec)->toUnicode(value);
       
   548         } else {
       
   549             *codec = defaultCodec;
       
   550             return defaultCodec->toUnicode(value);
       
   551         }
       
   552     }
       
   553     *codec = defaultCodec;
       
   554     return defaultCodec->toUnicode(value);
       
   555 }
       
   556 
       
   557 /*!
       
   558  * Decodes Quoted-Printable encoded (RFC 1521) characters in /a text.
       
   559  */
       
   560 void QVersitReaderPrivate::decodeQuotedPrintable(QString& text) const
       
   561 {
       
   562     for (int i=0; i < text.length(); i++) {
       
   563         QChar current = text.at(i);
       
   564         if (current == QLatin1Char('=') && i+2 < text.length()) {
       
   565             int next = text.at(i+1).unicode();
       
   566             int nextAfterNext = text.at(i+2).unicode();
       
   567             if (((next >= 'a' && next <= 'f') ||
       
   568                  (next >= 'A' && next <= 'F') ||
       
   569                  (next >= '0' && next <= '9')) &&
       
   570                 ((nextAfterNext >= 'a' && nextAfterNext <= 'f') ||
       
   571                  (nextAfterNext >= 'A' && nextAfterNext <= 'F') ||
       
   572                  (nextAfterNext >= '0' && nextAfterNext <= '9'))) {
       
   573                 bool ok;
       
   574                 QChar decodedChar(text.mid(i+1, 2).toInt(&ok,16));
       
   575                 if (ok)
       
   576                     text.replace(i, 3, decodedChar);
       
   577             } else if (next == '\r' && nextAfterNext == '\n') {
       
   578                 // Newlines can still be found here if they are encoded in a non-default charset.
       
   579                 text.remove(i, 3);
       
   580             }
       
   581         }
       
   582     }
       
   583 }
       
   584 
       
   585 
       
   586 /*!
       
   587  * Extracts the groups and the name of the property using \a codec to determine the delimiters
       
   588  *
       
   589  * On entry, \a line should select a whole line.
       
   590  * On exit, \a line will be updated to point after the groups and name.
       
   591  */
       
   592 QPair<QStringList,QString>QVersitReaderPrivate::extractPropertyGroupsAndName(
       
   593         VersitCursor& line, QTextCodec *codec) const
       
   594 {
       
   595     const QByteArray semicolon = VersitUtils::encode(';', codec);
       
   596     const QByteArray colon = VersitUtils::encode(':', codec);
       
   597     const QByteArray backslash = VersitUtils::encode('\\', codec);
       
   598     QPair<QStringList,QString> groupsAndName;
       
   599     int length = 0;
       
   600     Q_ASSERT(line.data.size() >= line.position);
       
   601 
       
   602     int separatorLength = semicolon.length();
       
   603     for (int i = line.position; i < line.selection - separatorLength + 1; i++) {
       
   604         if ((containsAt(line.data, semicolon, i)
       
   605                 && !containsAt(line.data, backslash, i-separatorLength))
       
   606             || containsAt(line.data, colon, i)) {
       
   607             length = i - line.position;
       
   608             break;
       
   609         }
       
   610     }
       
   611     if (length > 0) {
       
   612         QString trimmedGroupsAndName =
       
   613                 codec->toUnicode(line.data.mid(line.position, length)).trimmed();
       
   614         QStringList parts = trimmedGroupsAndName.split(QLatin1Char('.'));
       
   615         if (parts.count() > 1) {
       
   616             groupsAndName.second = parts.takeLast();
       
   617             groupsAndName.first = parts;
       
   618         } else {
       
   619             groupsAndName.second = trimmedGroupsAndName;
       
   620         }
       
   621         line.setPosition(length + line.position);
       
   622     }
       
   623 
       
   624     return groupsAndName;
       
   625 }
       
   626 
       
   627 /*!
       
   628  * Extracts the value of the property.
       
   629  * Returns an empty string if the value was not found.
       
   630  *
       
   631  * On entry \a line should point to the value anyway.
       
   632  * On exit \a line should point to newline after the value
       
   633  */
       
   634 QByteArray QVersitReaderPrivate::extractPropertyValue(VersitCursor& line) const
       
   635 {
       
   636     QByteArray value = line.data.mid(line.position, line.selection - line.position);
       
   637 
       
   638     /* Now advance the cursor in all cases. */
       
   639     line.position = line.selection;
       
   640     return value;
       
   641 }
       
   642 
       
   643 /*!
       
   644  * Extracts the property parameters as a QMultiHash using \a codec to determine the delimiters.
       
   645  * The parameters without names are added as "TYPE" parameters.
       
   646  *
       
   647  * On entry \a line should contain the entire line.
       
   648  * On exit, line will be updated to point to the start of the value.
       
   649  */
       
   650 QMultiHash<QString,QString> QVersitReaderPrivate::extractVCard21PropertyParams(
       
   651         VersitCursor& line, QTextCodec *codec) const
       
   652 {
       
   653     QMultiHash<QString,QString> result;
       
   654     QList<QByteArray> paramList = extractParams(line, codec);
       
   655     while (!paramList.isEmpty()) {
       
   656         QByteArray param = paramList.takeLast();
       
   657         QString name = paramName(param, codec);
       
   658         QString value = paramValue(param, codec);
       
   659         result.insert(name,value);
       
   660     }
       
   661 
       
   662     return result;
       
   663 }
       
   664 
       
   665 /*!
       
   666  * Extracts the property parameters as a QMultiHash using \a codec to determine the delimiters.
       
   667  * The parameters without names are added as "TYPE" parameters.
       
   668  */
       
   669 QMultiHash<QString,QString> QVersitReaderPrivate::extractVCard30PropertyParams(
       
   670         VersitCursor& line, QTextCodec *codec) const
       
   671 {
       
   672     QMultiHash<QString,QString> result;
       
   673     QList<QByteArray> paramList = extractParams(line, codec);
       
   674     while (!paramList.isEmpty()) {
       
   675         QByteArray param = paramList.takeLast();
       
   676         QString name(paramName(param, codec));
       
   677         VersitUtils::removeBackSlashEscaping(name);
       
   678         QString values = paramValue(param, codec);
       
   679         QList<QString> valueList = values.split(QLatin1Char(','), QString::SkipEmptyParts);
       
   680         QString buffer; // for any part ending in a backslash, join it to the next.
       
   681         foreach (QString value, valueList) {
       
   682             if (value.endsWith(QLatin1Char('\\')) && !value.endsWith(QLatin1String("\\\\"))) {
       
   683                 value.chop(1);
       
   684                 buffer.append(value);
       
   685                 buffer.append(QLatin1Char(',')); // because the comma got nuked by split()
       
   686             }
       
   687             else {
       
   688                 buffer.append(value);
       
   689                 VersitUtils::removeBackSlashEscaping(buffer);
       
   690                 result.insert(name, buffer);
       
   691                 buffer.clear();
       
   692             }
       
   693         }
       
   694     }
       
   695 
       
   696     return result;
       
   697 }
       
   698 
       
   699 
       
   700 /*!
       
   701  * Extracts the parameters as delimited by semicolons using \a codec to determine the delimiters.
       
   702  *
       
   703  * On entry \a line should point to the start of the parameter section (past the name).
       
   704  * On exit, \a line will be updated to point to the start of the value.
       
   705  */
       
   706 QList<QByteArray> QVersitReaderPrivate::extractParams(VersitCursor& line, QTextCodec *codec) const
       
   707 {
       
   708     const QByteArray colon = VersitUtils::encode(':', codec);
       
   709     QList<QByteArray> params;
       
   710 
       
   711     /* find the end of the name&params */
       
   712     int colonIndex = line.data.indexOf(colon, line.position);
       
   713     if (colonIndex > line.position && colonIndex < line.selection) {
       
   714         QByteArray nameAndParamsString = line.data.mid(line.position, colonIndex - line.position);
       
   715         params = extractParts(nameAndParamsString, VersitUtils::encode(';', codec), codec);
       
   716 
       
   717         /* Update line */
       
   718         line.setPosition(colonIndex + colon.length());
       
   719     } else if (colonIndex == line.position) {
       
   720         // No parameters.. advance past it
       
   721         line.setPosition(line.position + colon.length());
       
   722     }
       
   723 
       
   724     return params;
       
   725 }
       
   726 
       
   727 /*!
       
   728  * Extracts the parts separated by separator discarding the separators escaped with a backslash
       
   729  * encoded with \a codec
       
   730  */
       
   731 QList<QByteArray> QVersitReaderPrivate::extractParts(
       
   732         const QByteArray& text, const QByteArray& separator, QTextCodec* codec) const
       
   733 {
       
   734     QList<QByteArray> parts;
       
   735     int partStartIndex = 0;
       
   736     int textLength = text.length();
       
   737     int separatorLength = separator.length();
       
   738     QByteArray backslash = VersitUtils::encode('\\', codec);
       
   739     int backslashLength = backslash.length();
       
   740 
       
   741     for (int i=0; i < textLength-separatorLength+1; i++) {
       
   742         if (containsAt(text, separator, i)
       
   743             && (i < backslashLength
       
   744                 || !containsAt(text, backslash, i-backslashLength))) {
       
   745             int length = i-partStartIndex;
       
   746             QByteArray part = extractPart(text,partStartIndex,length);
       
   747             if (part.length() > 0)
       
   748                 parts.append(part);
       
   749             partStartIndex = i+separatorLength;
       
   750         }
       
   751     }
       
   752 
       
   753     // Add the last or only part
       
   754     QByteArray part = extractPart(text,partStartIndex);
       
   755     if (part.length() > 0)
       
   756         parts.append(part);
       
   757     return parts;
       
   758 }
       
   759 
       
   760 /*!
       
   761  * Extracts a substring limited by /a startPosition and /a length.
       
   762  */
       
   763 QByteArray QVersitReaderPrivate::extractPart(
       
   764         const QByteArray& text, int startPosition, int length) const
       
   765 {
       
   766     QByteArray part;
       
   767     if (startPosition >= 0)
       
   768         part = text.mid(startPosition,length).trimmed();
       
   769     return part;
       
   770 }
       
   771 
       
   772 /*!
       
   773  * Extracts the name of the parameter using \a codec to determine the delimiters.
       
   774  * No name is interpreted as an implicit "TYPE".
       
   775  */
       
   776 QString QVersitReaderPrivate::paramName(const QByteArray& parameter, QTextCodec* codec) const
       
   777 {
       
   778      if (parameter.trimmed().length() == 0)
       
   779          return QString();
       
   780      QByteArray equals = VersitUtils::encode('=', codec);
       
   781      int equalsIndex = parameter.indexOf(equals);
       
   782      if (equalsIndex > 0) {
       
   783          return codec->toUnicode(parameter.left(equalsIndex)).trimmed();
       
   784      }
       
   785 
       
   786      return QLatin1String("TYPE");
       
   787 }
       
   788 
       
   789 /*!
       
   790  * Extracts the value of the parameter using \a codec to determine the delimiters
       
   791  */
       
   792 QString QVersitReaderPrivate::paramValue(const QByteArray& parameter, QTextCodec* codec) const
       
   793 {
       
   794     QByteArray value(parameter);
       
   795     QByteArray equals = VersitUtils::encode('=', codec);
       
   796     int equalsIndex = parameter.indexOf(equals);
       
   797     if (equalsIndex > 0) {
       
   798         int valueLength = parameter.length() - (equalsIndex + equals.length());
       
   799         value = parameter.right(valueLength).trimmed();
       
   800     }
       
   801 
       
   802     return codec->toUnicode(value);
       
   803 }
       
   804 
       
   805 /*!
       
   806  * Returns true if and only if \a text contains \a ba at \a index
       
   807  *
       
   808  * On entry, index must be >= 0
       
   809  */
       
   810 bool QVersitReaderPrivate::containsAt(const QByteArray& text, const QByteArray& match, int index)
       
   811 {
       
   812     int n = match.length();
       
   813     if (text.length() - index < n)
       
   814         return false;
       
   815     const char* textData = text.constData();
       
   816     const char* matchData = match.constData();
       
   817     return memcmp(textData+index, matchData, n) == 0;
       
   818 }
       
   819 
       
   820 #include "moc_qversitreader_p.cpp"