src/versit/qversitreader_p.cpp
changeset 5 603d3f8b6302
parent 0 876b1a06bc25
equal deleted inserted replaced
3:e4ebb16b39ea 5:603d3f8b6302
    50 
    50 
    51 QTM_USE_NAMESPACE
    51 QTM_USE_NAMESPACE
    52 
    52 
    53 // Some big enough value for nested versit documents to prevent infinite recursion
    53 // Some big enough value for nested versit documents to prevent infinite recursion
    54 #define MAX_VERSIT_DOCUMENT_NESTING_DEPTH 20
    54 #define MAX_VERSIT_DOCUMENT_NESTING_DEPTH 20
       
    55 
       
    56 QHash<QPair<QVersitDocument::VersitType,QString>, QVersitProperty::ValueType>*
       
    57     QVersitReaderPrivate::mValueTypeMap = 0;
    55 
    58 
    56 /*!
    59 /*!
    57   \class LineReader
    60   \class LineReader
    58   \brief The LineReader class is a wrapper around a QIODevice that allows line-by-line reading.
    61   \brief The LineReader class is a wrapper around a QIODevice that allows line-by-line reading.
    59   \internal
    62   \internal
    70 LineReader::LineReader(QIODevice* device, QTextCodec *codec, int chunkSize)
    73 LineReader::LineReader(QIODevice* device, QTextCodec *codec, int chunkSize)
    71     : mDevice(device),
    74     : mDevice(device),
    72     mCodec(codec),
    75     mCodec(codec),
    73     mChunkSize(chunkSize),
    76     mChunkSize(chunkSize),
    74     mCrlfList(*VersitUtils::newlineList(mCodec)),
    77     mCrlfList(*VersitUtils::newlineList(mCodec)),
    75     mBuffer(VersitCursor(QByteArray())),
    78     mBuffer(LByteArray(QByteArray())),
    76     mOdometer(0)
    79     mOdometer(0),
    77 {
    80     mSearchFrom(0)
    78 }
    81 {
    79 
    82 }
    80 /*!
    83 
    81   Attempts to read a line and returns a VersitCursor describing the line.  The cursor returned
    84 /*!
    82   includes the data, as well as the position and selection index bounds.  Data within those bounds
    85   Attempts to read a line and returns an LByteArray containing the line.
    83   represents the line.  Data outside those bounds should not be used.
    86   */
    84  */
    87 LByteArray LineReader::readLine()
    85 VersitCursor LineReader::readLine()
    88 {
    86 {
    89     if (!mFirstLine.isEmpty()) {
    87     mBuffer.position = mBuffer.selection;
    90         LByteArray retval(mFirstLine);
    88     mSearchFrom = mBuffer.position;
    91         mFirstLine.clear();
       
    92         return retval;
       
    93     }
       
    94     mBuffer.mStart = mBuffer.mEnd;
       
    95     mSearchFrom = mBuffer.mStart;
    89 
    96 
    90     // First, look for a newline in the already-existing buffer.  If found, return the line.
    97     // First, look for a newline in the already-existing buffer.  If found, return the line.
    91     if (tryReadLine(mBuffer, false)) {
    98     if (tryReadLine(mBuffer, false)) {
    92         mBuffer.dropOldData();
    99         mBuffer.dropOldData();
    93         mOdometer += mBuffer.selection - mBuffer.position;
   100         mOdometer += mBuffer.size();
    94         return mBuffer;
   101         return mBuffer;
    95     }
   102     }
    96 
   103 
    97     // Otherwise, keep reading more data until either a CRLF is found, or there's no more to read.
   104     // Otherwise, keep reading more data until either a CRLF is found, or there's no more to read.
    98     while (!mDevice->atEnd()) {
   105     while (!mDevice->atEnd()) {
    99         QByteArray temp = mDevice->read(mChunkSize);
   106         QByteArray temp = mDevice->read(mChunkSize);
   100         if (!temp.isEmpty()) {
   107         if (!temp.isEmpty()) {
   101             mBuffer.data.append(temp);
   108             mBuffer.mData.append(temp);
   102             if (tryReadLine(mBuffer, false)) {
   109             if (tryReadLine(mBuffer, false)) {
   103                 mBuffer.dropOldData();
   110                 mBuffer.dropOldData();
   104                 mOdometer += mBuffer.selection - mBuffer.position;
   111                 mOdometer += mBuffer.size();
   105                 return mBuffer;
   112                 return mBuffer;
   106             }
   113             }
   107         } else {
   114         } else {
   108             mDevice->waitForReadyRead(500);
   115             mDevice->waitForReadyRead(500);
   109         }
   116         }
   110     }
   117     }
   111 
   118 
   112     // We've reached the end of the stream.  Find a newline from the buffer (or return what's left).
   119     // We've reached the end of the stream.  Find a newline from the buffer (or return what's left).
   113     tryReadLine(mBuffer, true);
   120     tryReadLine(mBuffer, true);
   114     mBuffer.dropOldData();
   121     mBuffer.dropOldData();
   115     mOdometer += mBuffer.selection - mBuffer.position;
   122     mOdometer += mBuffer.size();
   116     return mBuffer;
   123     return mBuffer;
   117 }
   124 }
   118 
   125 
   119 /*!
   126 /*!
   120   How many bytes have been returned in the VersitCursor in the lifetime of the LineReader.
   127   Push a line onto the front of the line reader so it will be returned on the next call to readLine().
       
   128   */
       
   129 void LineReader::pushLine(const QByteArray& line)
       
   130 {
       
   131     mFirstLine = line;
       
   132 }
       
   133 
       
   134 /*!
       
   135   How many bytes have been returned in the LByteArray in the lifetime of the LineReader.
   121  */
   136  */
   122 int LineReader::odometer()
   137 int LineReader::odometer()
   123 {
   138 {
   124     return mOdometer;
   139     return mOdometer;
   125 }
   140 }
   126 
   141 
   127 /*!
   142 /*!
   128   Returns true if there are no more lines left for readLine() to return.  It is possible for atEnd()
   143   Returns true if there are no more lines left for readLine() to return.  It is possible for atEnd()
   129   to return false and for there to be no more data left (eg. if there are trailing newlines at the
   144   to return false and for there to be no more data left (eg. if there are trailing newlines at the
   130   end of the input.  In this case, readLine() will return an empty line (ie. position == selection).
   145   end of the input.  In this case, readLine() will return an empty line.
   131  */
   146  */
   132 bool LineReader::atEnd()
   147 bool LineReader::atEnd()
   133 {
   148 {
   134     return mDevice->atEnd() && mBuffer.selection == mBuffer.data.size();
   149     return mFirstLine.isEmpty() && mDevice->atEnd() && mBuffer.mEnd == mBuffer.mData.size();
   135 }
   150 }
   136 
   151 
   137 /*!
   152 /*!
   138   Returns the codec that the LineReader reads with.
   153   Returns the codec that the LineReader reads with.
   139  */
   154  */
   145 /*!
   160 /*!
   146  * Get the next line of input from \a device to parse.  Also performs unfolding by removing
   161  * Get the next line of input from \a device to parse.  Also performs unfolding by removing
   147  * sequences of newline-space from the retrieved line.  Skips over any newlines at the start of the
   162  * sequences of newline-space from the retrieved line.  Skips over any newlines at the start of the
   148  * input.
   163  * input.
   149  *
   164  *
   150  * Returns a VersitCursor containing and selecting the line.
   165  * Returns an LByteArray containing the line.
   151  */
   166  */
   152 bool LineReader::tryReadLine(VersitCursor &cursor, bool atEnd)
   167 bool LineReader::tryReadLine(LByteArray &cursor, bool atEnd)
   153 {
   168 {
   154     int crlfPos = -1;
   169     int crlfPos = -1;
   155 
   170 
   156     QByteArray space = VersitUtils::encode(' ', mCodec);
   171     QByteArray space = VersitUtils::encode(' ', mCodec);
   157     QByteArray tab = VersitUtils::encode('\t', mCodec);
   172     QByteArray tab = VersitUtils::encode('\t', mCodec);
   158     int spaceLength = space.length();
   173     int spaceLength = space.length();
   159 
   174 
   160     forever {
   175     forever {
   161         foreach(const QByteArrayMatcher& crlf, mCrlfList) {
   176         foreach(const QByteArrayMatcher& crlf, mCrlfList) {
   162             int crlfLength = crlf.pattern().length();
   177             int crlfLength = crlf.pattern().length();
   163             crlfPos = crlf.indexIn(cursor.data, mSearchFrom);
   178             crlfPos = crlf.indexIn(cursor.mData, mSearchFrom);
   164             if (crlfPos == cursor.position) {
   179             if (crlfPos == cursor.mStart) {
   165                 // Newline at start of line.  Set position to directly after it.
   180                 // Newline at start of line.  Set mStart to directly after it.
   166                 cursor.position += crlfLength;
   181                 cursor.mStart += crlfLength;
   167                 mSearchFrom = cursor.position;
   182                 mSearchFrom = cursor.mStart;
   168                 break;
   183                 break;
   169             } else if (crlfPos > cursor.position) {
   184             } else if (crlfPos > cursor.mStart) {
   170                 // Found the CRLF.
   185                 // Found the CRLF.
   171                 if (QVersitReaderPrivate::containsAt(cursor.data, space, crlfPos + crlfLength)
   186                 if (QVersitReaderPrivate::containsAt(cursor.mData, space, crlfPos + crlfLength)
   172                     || QVersitReaderPrivate::containsAt(cursor.data, tab, crlfPos + crlfLength)) {
   187                     || QVersitReaderPrivate::containsAt(cursor.mData, tab, crlfPos + crlfLength)) {
   173                     // If it's followed by whitespace, collapse it.
   188                     // If it's followed by whitespace, collapse it.
   174                     cursor.data.remove(crlfPos, crlfLength + spaceLength);
   189                     cursor.mData.remove(crlfPos, crlfLength + spaceLength);
   175                     mSearchFrom = crlfPos;
   190                     mSearchFrom = crlfPos;
   176                     break;
   191                     break;
   177                 } else if (!atEnd && crlfPos + crlfLength + spaceLength >= cursor.data.size()) {
   192                 } else if (!atEnd && crlfPos + crlfLength + spaceLength >= cursor.mData.size()) {
   178                     // If our CRLF is at the end of the current buffer but there's more to read,
   193                     // If our CRLF is at the end of the current buffer but there's more to read,
   179                     // it's possible that a space could be hiding on the next read from the device.
   194                     // it's possible that a space could be hiding on the next read from the device.
   180                     // Just pretend we didn't see the CRLF and pick it up the next time round.
   195                     // Just pretend we didn't see the CRLF and pick it up the next time round.
   181                     mSearchFrom = crlfPos;
   196                     mSearchFrom = crlfPos;
   182                     return false;
   197                     return false;
   183                 } else {
   198                 } else {
   184                     // Found the CRLF.
   199                     // Found the CRLF.
   185                     cursor.selection = crlfPos;
   200                     cursor.mEnd = crlfPos;
   186                     return true;
   201                     return true;
   187                 }
   202                 }
   188             }
   203             }
   189         }
   204         }
   190         if (crlfPos == -1) {
   205         if (crlfPos == -1) {
   191             // No CRLF found.
   206             // No CRLF found.
   192             cursor.selection = cursor.data.size();
   207             cursor.mEnd = cursor.mData.size();
   193             return false;
   208             return false;
   194         }
   209         }
   195     }
   210     }
   196 }
   211 }
   197 
   212 
   212     mDefaultCodec(QTextCodec::codecForName("UTF-8")),
   227     mDefaultCodec(QTextCodec::codecForName("UTF-8")),
   213     mState(QVersitReader::InactiveState),
   228     mState(QVersitReader::InactiveState),
   214     mError(QVersitReader::NoError),
   229     mError(QVersitReader::NoError),
   215     mIsCanceling(false)
   230     mIsCanceling(false)
   216 {
   231 {
   217     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("AGENT")),
       
   218                          QVersitProperty::VersitDocumentType);
       
   219     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("AGENT")),
       
   220                          QVersitProperty::VersitDocumentType);
       
   221     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("N")),
       
   222                          QVersitProperty::CompoundType);
       
   223     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("N")),
       
   224                          QVersitProperty::CompoundType);
       
   225     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("ADR")),
       
   226                          QVersitProperty::CompoundType);
       
   227     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("ADR")),
       
   228                          QVersitProperty::CompoundType);
       
   229     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("GEO")),
       
   230                          QVersitProperty::CompoundType);
       
   231     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("GEO")),
       
   232                          QVersitProperty::CompoundType);
       
   233     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("ORG")),
       
   234                          QVersitProperty::CompoundType);
       
   235     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("ORG")),
       
   236                          QVersitProperty::CompoundType);
       
   237     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("NICKNAME")),
       
   238                          QVersitProperty::ListType);
       
   239     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("NICKNAME")),
       
   240                          QVersitProperty::ListType);
       
   241     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("CATEGORIES")),
       
   242                          QVersitProperty::ListType);
       
   243     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("CATEGORIES")),
       
   244                          QVersitProperty::ListType);
       
   245     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("X-CHILDREN")),
       
   246                          QVersitProperty::ListType);
       
   247     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("X-CHILDREN")),
       
   248                          QVersitProperty::ListType);
       
   249     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("X-NICKNAME")),
       
   250                          QVersitProperty::ListType);
       
   251     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("X-NICKNAME")),
       
   252                          QVersitProperty::ListType);
       
   253 }
   232 }
   254 
   233 
   255 /*! Destroy a reader. */
   234 /*! Destroy a reader. */
   256 QVersitReaderPrivate::~QVersitReaderPrivate()
   235 QVersitReaderPrivate::~QVersitReaderPrivate()
   257 {
   236 {
       
   237 }
       
   238 
       
   239 QHash<QPair<QVersitDocument::VersitType,QString>, QVersitProperty::ValueType>*
       
   240 QVersitReaderPrivate::valueTypeMap() {
       
   241     if (mValueTypeMap == 0) {
       
   242         mValueTypeMap = new QHash<QPair<QVersitDocument::VersitType,QString>, QVersitProperty::ValueType>();
       
   243         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("AGENT")),
       
   244                              QVersitProperty::VersitDocumentType);
       
   245         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("AGENT")),
       
   246                              QVersitProperty::VersitDocumentType);
       
   247         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard40Type, QString::fromAscii("AGENT")),
       
   248                              QVersitProperty::VersitDocumentType);
       
   249         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("N")),
       
   250                              QVersitProperty::CompoundType);
       
   251         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("N")),
       
   252                              QVersitProperty::CompoundType);
       
   253         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard40Type, QString::fromAscii("N")),
       
   254                              QVersitProperty::CompoundType);
       
   255         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("ADR")),
       
   256                              QVersitProperty::CompoundType);
       
   257         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("ADR")),
       
   258                              QVersitProperty::CompoundType);
       
   259         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard40Type, QString::fromAscii("ADR")),
       
   260                              QVersitProperty::CompoundType);
       
   261         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("GEO")),
       
   262                              QVersitProperty::CompoundType);
       
   263         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("GEO")),
       
   264                              QVersitProperty::CompoundType);
       
   265         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard40Type, QString::fromAscii("GEO")),
       
   266                              QVersitProperty::CompoundType);
       
   267         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("ORG")),
       
   268                              QVersitProperty::CompoundType);
       
   269         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("ORG")),
       
   270                              QVersitProperty::CompoundType);
       
   271         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard40Type, QString::fromAscii("ORG")),
       
   272                              QVersitProperty::CompoundType);
       
   273         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("NICKNAME")),
       
   274                              QVersitProperty::ListType);
       
   275         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("NICKNAME")),
       
   276                              QVersitProperty::ListType);
       
   277         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard40Type, QString::fromAscii("NICKNAME")),
       
   278                              QVersitProperty::ListType);
       
   279         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("CATEGORIES")),
       
   280                              QVersitProperty::ListType);
       
   281         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("CATEGORIES")),
       
   282                              QVersitProperty::ListType);
       
   283         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard40Type, QString::fromAscii("CATEGORIES")),
       
   284                              QVersitProperty::ListType);
       
   285         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("X-CHILDREN")),
       
   286                              QVersitProperty::ListType);
       
   287         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("X-CHILDREN")),
       
   288                              QVersitProperty::ListType);
       
   289         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard40Type, QString::fromAscii("X-CHILDREN")),
       
   290                              QVersitProperty::ListType);
       
   291         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("X-NICKNAME")),
       
   292                              QVersitProperty::ListType);
       
   293         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("X-NICKNAME")),
       
   294                              QVersitProperty::ListType);
       
   295         mValueTypeMap->insert(qMakePair(QVersitDocument::VCard40Type, QString::fromAscii("X-NICKNAME")),
       
   296                              QVersitProperty::ListType);
       
   297     }
       
   298     return mValueTypeMap;
   258 }
   299 }
   259 
   300 
   260 /*!
   301 /*!
   261  * Inherited from QThread, called by QThread when the thread has been started.
   302  * Inherited from QThread, called by QThread when the thread has been started.
   262  */
   303  */
   345 }
   386 }
   346 
   387 
   347 /*!
   388 /*!
   348  * Parses a versit document. Returns true if the parsing was successful.
   389  * Parses a versit document. Returns true if the parsing was successful.
   349  */
   390  */
   350 bool QVersitReaderPrivate::parseVersitDocument(LineReader& lineReader, QVersitDocument& document,
   391 bool QVersitReaderPrivate::parseVersitDocument(LineReader& lineReader, QVersitDocument& document)
   351                                                bool foundBegin)
       
   352 {
   392 {
   353     if (mDocumentNestingLevel >= MAX_VERSIT_DOCUMENT_NESTING_DEPTH)
   393     if (mDocumentNestingLevel >= MAX_VERSIT_DOCUMENT_NESTING_DEPTH)
   354         return false; // To prevent infinite recursion
   394         return false; // To prevent infinite recursion
   355 
   395 
       
   396     // If we don't know what type it is, just assume it's a vCard 3.0
       
   397     if (document.type() == QVersitDocument::InvalidType)
       
   398         document.setType(QVersitDocument::VCard30Type);
       
   399 
       
   400     QVersitProperty property;
       
   401 
       
   402     property = parseNextVersitProperty(document.type(), lineReader);
       
   403     QString propertyValue = property.value().trimmed().toUpper();
       
   404     if (property.isEmpty()) {
       
   405         // A blank document (or end of file) was found.
       
   406         document = QVersitDocument();
       
   407         return true;
       
   408     } else if (property.name() == QLatin1String("BEGIN")) {
       
   409         if (propertyValue == QLatin1String("VCARD")) {
       
   410             document.setComponentType(propertyValue);
       
   411         } else if (propertyValue == QLatin1String("VCALENDAR")) {
       
   412             document.setType(QVersitDocument::ICalendar20Type);
       
   413             document.setComponentType(propertyValue);
       
   414         } else {
       
   415             // Unknown document type
       
   416             document = QVersitDocument();
       
   417             return false;
       
   418         }
       
   419     } else {
       
   420         // Some property other than BEGIN was found.
       
   421         document = QVersitDocument();
       
   422         return false;
       
   423     }
       
   424 
       
   425     return parseVersitDocumentBody(lineReader, document);
       
   426 }
       
   427 
       
   428 bool QVersitReaderPrivate::parseVersitDocumentBody(LineReader& lineReader, QVersitDocument& document)
       
   429 {
       
   430     mDocumentNestingLevel++;
   356     bool parsingOk = true;
   431     bool parsingOk = true;
   357     mDocumentNestingLevel++;
   432     while (true) {
   358 
   433         /* Grab it */
   359     // TODO: Various readers should be made subclasses and eliminate assumptions like this.
   434         QVersitProperty property = parseNextVersitProperty(document.type(), lineReader);
   360     // We don't know what type it is: just assume it's a vCard 3.0
   435 
   361     document.setType(QVersitDocument::VCard30Type);
   436         if (property.name() == QLatin1String("BEGIN")) {
   362 
   437             // Nested Versit document
   363     QVersitProperty property;
   438             QVersitDocument subDocument;
   364 
   439             subDocument.setType(document.type());
   365     if (!foundBegin) {
   440             subDocument.setComponentType(property.value().trimmed().toUpper());
   366         property = parseNextVersitProperty(document.type(), lineReader);
   441             if (!parseVersitDocumentBody(lineReader, subDocument))
   367         if (property.name() == QLatin1String("BEGIN")
   442                 break;
   368             && property.value().trimmed().toUpper() == QLatin1String("VCARD")) {
   443             document.addSubDocument(subDocument);
   369             foundBegin = true;
   444         } else if (property.name() == QLatin1String("VERSION")) {
   370         } else if (property.isEmpty()) {
   445             // A version property
   371             // A blank document (or end of file) was found.
       
   372             document = QVersitDocument();
       
   373         } else {
       
   374             // Some property other than BEGIN was found.
       
   375             parsingOk = false;
       
   376         }
       
   377     }
       
   378 
       
   379     if (foundBegin) {
       
   380         do {
       
   381             /* Grab it */
       
   382             property = parseNextVersitProperty(document.type(), lineReader);
       
   383 
       
   384             /* Discard embedded vcard documents - not supported yet.  Discard the entire vCard */
       
   385             if (property.name() == QLatin1String("BEGIN") &&
       
   386                 QString::compare(property.value().trimmed(),
       
   387                                  QLatin1String("VCARD"), Qt::CaseInsensitive) == 0) {
       
   388                 parsingOk = false;
       
   389                 QVersitDocument nestedDocument;
       
   390                 if (!parseVersitDocument(lineReader, nestedDocument, true))
       
   391                     break;
       
   392             }
       
   393 
       
   394             // See if this is a version property and continue parsing under that version
       
   395             if (!setVersionFromProperty(document, property)) {
   446             if (!setVersionFromProperty(document, property)) {
   396                 parsingOk = false;
   447                 parsingOk = false;
   397                 break;
   448                 break;
   398             }
   449             }
   399 
   450         } else if (property.name() == QLatin1String("END")) {
   400             /* Nope, something else.. just add it */
   451             // End of document
   401             if (property.name() != QLatin1String("VERSION") &&
   452             break;
   402                 property.name() != QLatin1String("END"))
   453         } else if (property.name().isEmpty()) {
   403                 document.addProperty(property);
   454             // End of input or some other error
   404         } while (property.name().length() > 0 && property.name() != QLatin1String("END"));
       
   405         if (property.name() != QLatin1String("END"))
       
   406             parsingOk = false;
   455             parsingOk = false;
   407     }
   456             break;
   408     mDocumentNestingLevel--;
   457         } else {
       
   458             // A normal property - just add it.
       
   459             document.addProperty(property);
       
   460         }
       
   461     }
   409     if (!parsingOk)
   462     if (!parsingOk)
   410         document = QVersitDocument();
   463         document = QVersitDocument();
       
   464     mDocumentNestingLevel--;
   411 
   465 
   412     return parsingOk;
   466     return parsingOk;
   413 }
   467 }
   414 
   468 
   415 /*!
   469 /*!
   417  */
   471  */
   418 QVersitProperty QVersitReaderPrivate::parseNextVersitProperty(
   472 QVersitProperty QVersitReaderPrivate::parseNextVersitProperty(
   419         QVersitDocument::VersitType versitType,
   473         QVersitDocument::VersitType versitType,
   420         LineReader& lineReader)
   474         LineReader& lineReader)
   421 {
   475 {
   422     VersitCursor cursor = lineReader.readLine();
   476     LByteArray cursor = lineReader.readLine();
   423     if (cursor.position >= cursor.selection)
   477     if (cursor.isEmpty())
   424         return QVersitProperty();
   478         return QVersitProperty();
   425 
   479 
   426     // Otherwise, do stuff.
   480     // Otherwise, do stuff.
   427     QPair<QStringList,QString> groupsAndName =
   481     QPair<QStringList,QString> groupsAndName =
   428             extractPropertyGroupsAndName(cursor, lineReader.codec());
   482             extractPropertyGroupsAndName(cursor, lineReader.codec());
   431     property.setGroups(groupsAndName.first);
   485     property.setGroups(groupsAndName.first);
   432     property.setName(groupsAndName.second);
   486     property.setName(groupsAndName.second);
   433     // set the propertyValueType
   487     // set the propertyValueType
   434     QPair<QVersitDocument::VersitType, QString> key =
   488     QPair<QVersitDocument::VersitType, QString> key =
   435         qMakePair(versitType, property.name());
   489         qMakePair(versitType, property.name());
   436     if (mValueTypeMap.contains(key))
   490     if (valueTypeMap()->contains(key))
   437         property.setValueType(mValueTypeMap.value(key));
   491         property.setValueType(valueTypeMap()->value(key));
   438 
   492 
   439     if (versitType == QVersitDocument::VCard21Type)
   493     if (versitType == QVersitDocument::VCard21Type)
   440         parseVCard21Property(cursor, property, lineReader);
   494         parseVCard21Property(cursor, property, lineReader);
   441     else if (versitType == QVersitDocument::VCard30Type)
   495     else if (versitType == QVersitDocument::VCard30Type
   442         parseVCard30Property(cursor, property, lineReader);
   496             || versitType == QVersitDocument::VCard40Type
       
   497             || versitType == QVersitDocument::ICalendar20Type)
       
   498         parseVCard30Property(versitType, cursor, property, lineReader);
   443 
   499 
   444     return property;
   500     return property;
   445 }
   501 }
   446 
   502 
   447 /*!
   503 /*!
   448  * Parses the property according to vCard 2.1 syntax.
   504  * Parses the property according to vCard 2.1 syntax.
   449  */
   505  */
   450 void QVersitReaderPrivate::parseVCard21Property(VersitCursor& cursor, QVersitProperty& property,
   506 void QVersitReaderPrivate::parseVCard21Property(LByteArray& cursor, QVersitProperty& property,
   451                                                 LineReader& lineReader)
   507                                                 LineReader& lineReader)
   452 {
   508 {
   453     property.setParameters(extractVCard21PropertyParams(cursor, lineReader.codec()));
   509     property.setParameters(extractVCard21PropertyParams(cursor, lineReader.codec()));
   454 
   510 
   455     QByteArray value = extractPropertyValue(cursor);
   511     QByteArray value = cursor.toByteArray();
   456     if (property.valueType() == QVersitProperty::VersitDocumentType) {
   512     if (property.valueType() == QVersitProperty::VersitDocumentType) {
   457         // Hack to handle cases where start of document is on the same or next line as "AGENT:"
   513         // Hack to handle cases where start of document is on the same or next line as "AGENT:"
   458         bool foundBegin = false;
       
   459         if (value == "BEGIN:VCARD") {
   514         if (value == "BEGIN:VCARD") {
   460             foundBegin = true;
   515             lineReader.pushLine(value);
   461         } else if (value.isEmpty()) {
   516         } else if (value.isEmpty()) {
   462         } else {
   517         } else {
   463             property = QVersitProperty();
   518             property = QVersitProperty();
   464             return;
   519             return;
   465         }
   520         }
   466         QVersitDocument subDocument;
   521         QVersitDocument subDocument(QVersitDocument::VCard21Type);
   467         if (!parseVersitDocument(lineReader, subDocument, foundBegin)) {
   522         if (!parseVersitDocument(lineReader, subDocument)) {
   468             property = QVersitProperty();
   523             property = QVersitProperty();
   469         } else {
   524         } else {
   470             property.setValue(QVariant::fromValue(subDocument));
   525             property.setValue(QVariant::fromValue(subDocument));
   471         }
   526         }
   472     } else {
   527     } else {
   473         QTextCodec* codec;
   528         QTextCodec* codec;
   474         bool isBinary = unencode(value, cursor, property, lineReader);
   529         bool isBinary = unencode(value, property, lineReader);
   475         if (isBinary) {
   530         if (isBinary) {
   476             property.setValue(value);
   531             property.setValue(value);
   477             property.setValueType(QVersitProperty::BinaryType);
   532             property.setValueType(QVersitProperty::BinaryType);
   478         }
   533         }
   479         else {
   534         else {
   482         }
   537         }
   483     }
   538     }
   484 }
   539 }
   485 
   540 
   486 /*!
   541 /*!
   487  * Parses the property according to vCard 3.0 syntax.
   542  * Parses the property according to vCard 3.0 syntax.  This function is called for both vCard 3.0
   488  */
   543  * and iCalendar properties.
   489 void QVersitReaderPrivate::parseVCard30Property(VersitCursor& cursor, QVersitProperty& property,
   544  */
       
   545 void QVersitReaderPrivate::parseVCard30Property(QVersitDocument::VersitType versitType,
       
   546                                                 LByteArray& cursor, QVersitProperty& property,
   490                                                 LineReader& lineReader)
   547                                                 LineReader& lineReader)
   491 {
   548 {
   492     property.setParameters(extractVCard30PropertyParams(cursor, lineReader.codec()));
   549     property.setParameters(extractVCard30PropertyParams(cursor, lineReader.codec()));
   493 
   550 
   494     QByteArray value = extractPropertyValue(cursor);
   551     QByteArray value = cursor.toByteArray();
   495 
   552 
   496     QTextCodec* codec;
   553     QTextCodec* codec;
   497 
   554 
   498     if (property.valueType() == QVersitProperty::VersitDocumentType) {
   555     if (property.valueType() == QVersitProperty::VersitDocumentType) {
   499         QString valueString(decodeCharset(value, property, lineReader.codec(), &codec));
   556         QString valueString(decodeCharset(value, property, lineReader.codec(), &codec));
   503         QBuffer subDocumentData(&subDocumentValue);
   560         QBuffer subDocumentData(&subDocumentValue);
   504         subDocumentData.open(QIODevice::ReadOnly);
   561         subDocumentData.open(QIODevice::ReadOnly);
   505         subDocumentData.seek(0);
   562         subDocumentData.seek(0);
   506         LineReader subDocumentLineReader(&subDocumentData, codec);
   563         LineReader subDocumentLineReader(&subDocumentData, codec);
   507 
   564 
   508         QVersitDocument subDocument;
   565         QVersitDocument subDocument(versitType);
   509         if (!parseVersitDocument(subDocumentLineReader, subDocument)) {
   566         if (!parseVersitDocument(subDocumentLineReader, subDocument)) {
   510             property = QVersitProperty();
   567             property = QVersitProperty();
   511         } else {
   568         } else {
   512             property.setValue(QVariant::fromValue(subDocument));
   569             property.setValue(QVariant::fromValue(subDocument));
   513         }
   570         }
   514     } else {
   571     } else {
   515         bool isBinary = unencode(value, cursor, property, lineReader);
   572         bool isBinary = unencode(value, property, lineReader);
   516         if (isBinary) {
   573         if (isBinary) {
   517             property.setValue(value);
   574             property.setValue(value);
   518             property.setValueType(QVersitProperty::BinaryType);
   575             property.setValueType(QVersitProperty::BinaryType);
   519         } else {
   576         } else {
   520             property.setValue(decodeCharset(value, property, lineReader.codec(), &codec));
   577             property.setValue(decodeCharset(value, property, lineReader.codec(), &codec));
   538 /*!
   595 /*!
   539  * Sets version to \a document if \a property contains a supported version.
   596  * Sets version to \a document if \a property contains a supported version.
   540  */
   597  */
   541 bool QVersitReaderPrivate::setVersionFromProperty(QVersitDocument& document, const QVersitProperty& property) const
   598 bool QVersitReaderPrivate::setVersionFromProperty(QVersitDocument& document, const QVersitProperty& property) const
   542 {
   599 {
   543     bool valid = true;
   600     QString value = property.value().trimmed();
   544     if (property.name() == QLatin1String("VERSION")) {
   601     if (document.componentType() == QLatin1String("VCARD")
   545         QString value = property.value().trimmed();
   602             && value == QLatin1String("2.1")) {
   546         QStringList encodingParameters = property.parameters().values(QLatin1String("ENCODING"));
   603         document.setType(QVersitDocument::VCard21Type);
   547         QStringList typeParameters = property.parameters().values(QLatin1String("TYPE"));
   604     } else if (document.componentType() == QLatin1String("VCARD")
   548         if (encodingParameters.contains(QLatin1String("BASE64"), Qt::CaseInsensitive)
   605             && value == QLatin1String("3.0")) {
   549             || typeParameters.contains(QLatin1String("BASE64"), Qt::CaseInsensitive))
   606         document.setType(QVersitDocument::VCard30Type);
   550             value = QLatin1String(QByteArray::fromBase64(value.toAscii()));
   607     } else if (document.componentType() == QLatin1String("VCARD")
   551         if (value == QLatin1String("2.1")) {
   608             && value == QLatin1String("4.0")) {
   552             document.setType(QVersitDocument::VCard21Type);
   609         document.setType(QVersitDocument::VCard40Type);
   553         } else if (value == QLatin1String("3.0")) {
   610     } else if ((document.componentType() == QLatin1String("VCALENDAR")
   554             document.setType(QVersitDocument::VCard30Type);
   611                 || document.type() == QVersitDocument::ICalendar20Type) // covers VEVENT, etc. when nested inside a VCALENDAR
   555         } else {
   612             && value == QLatin1String("2.0")) {
   556             valid = false;
   613         document.setType(QVersitDocument::ICalendar20Type);
   557         }
   614     } else {
   558     }
   615         return false;
   559     return valid;
   616     }
       
   617     return true;
   560 }
   618 }
   561 
   619 
   562 /*!
   620 /*!
   563  * On entry, \a value should be the byte array to unencode.  It is modified to be the unencoded
   621  * On entry, \a value should be the byte array to unencode.  It is modified to be the unencoded
   564  * version.  Returns true if and only if the value was base-64 encoded.  \a cursor and
   622  * version.  Returns true if and only if the value was base-64 encoded.
   565  * \a lineReader are supplied in case more lines need to be read (for quoted-printable).  The
   623  * \a lineReader is supplied in case more lines need to be read (for quoted-printable).  The
   566  * \a property is supplied so we know what kind of encoding was used.
   624  * \a property is supplied so we know what kind of encoding was used.
   567  */
   625  */
   568 bool QVersitReaderPrivate::unencode(QByteArray& value, VersitCursor& cursor,
   626 bool QVersitReaderPrivate::unencode(QByteArray& value,
   569                                     QVersitProperty& property,
   627                                     QVersitProperty& property,
   570                                     LineReader& lineReader) const
   628                                     LineReader& lineReader) const
   571 {
   629 {
   572     QStringList encodingParameters = property.parameters().values(QLatin1String("ENCODING"));
   630     QStringList encodingParameters = property.parameters().values(QLatin1String("ENCODING"));
   573     QStringList typeParameters = property.parameters().values(QLatin1String("TYPE"));
   631     QStringList typeParameters = property.parameters().values(QLatin1String("TYPE"));
   575         // At this point, we need to accumulate bytes until we hit a real line break (no = before
   633         // At this point, we need to accumulate bytes until we hit a real line break (no = before
   576         // it) value already contains everything up to the character before the newline
   634         // it) value already contains everything up to the character before the newline
   577         while (value.endsWith('=')) {
   635         while (value.endsWith('=')) {
   578             value.chop(1); // Get rid of '='
   636             value.chop(1); // Get rid of '='
   579             // We add each line (minus the escaped = and newline chars)
   637             // We add each line (minus the escaped = and newline chars)
   580             cursor = lineReader.readLine();
   638             value.append(lineReader.readLine().toByteArray());
   581             QByteArray line = cursor.data.mid(cursor.position, cursor.selection-cursor.position);
       
   582             value.append(line);
       
   583         }
   639         }
   584         decodeQuotedPrintable(value);
   640         decodeQuotedPrintable(value);
   585         // Remove the encoding parameter as the value is now decoded
   641         // Remove the encoding parameter as the value is now decoded
   586         property.removeParameters(QLatin1String("ENCODING"));
   642         property.removeParameters(QLatin1String("ENCODING"));
   587         return false;
   643         return false;
   653 }
   709 }
   654 
   710 
   655 /*!
   711 /*!
   656  * Extracts the groups and the name of the property using \a codec to determine the delimiters
   712  * Extracts the groups and the name of the property using \a codec to determine the delimiters
   657  *
   713  *
   658  * On entry, \a line should select a whole line.
   714  * On entry, \a line should contain a whole line
   659  * On exit, \a line will be updated to point after the groups and name.
   715  * On exit, \a line will be updated to remove the groups and name
   660  */
   716  */
   661 QPair<QStringList,QString>QVersitReaderPrivate::extractPropertyGroupsAndName(
   717 QPair<QStringList,QString>QVersitReaderPrivate::extractPropertyGroupsAndName(
   662         VersitCursor& line, QTextCodec *codec) const
   718         LByteArray& line, QTextCodec *codec) const
   663 {
   719 {
   664     const QByteArray semicolon = VersitUtils::encode(';', codec);
   720     const QByteArray semicolon = VersitUtils::encode(';', codec);
   665     const QByteArray colon = VersitUtils::encode(':', codec);
   721     const QByteArray colon = VersitUtils::encode(':', codec);
   666     const QByteArray backslash = VersitUtils::encode('\\', codec);
   722     const QByteArray backslash = VersitUtils::encode('\\', codec);
   667     QPair<QStringList,QString> groupsAndName;
   723     QPair<QStringList,QString> groupsAndName;
   668     int length = 0;
   724     int length = 0;
   669     Q_ASSERT(line.data.size() >= line.position);
       
   670 
   725 
   671     int separatorLength = semicolon.length();
   726     int separatorLength = semicolon.length();
   672     for (int i = line.position; i < line.selection - separatorLength + 1; i++) {
   727     for (int i = 0; i < line.size() - separatorLength + 1; i++) {
   673         if ((containsAt(line.data, semicolon, i)
   728         if ((containsAt(line, semicolon, i) && !containsAt(line, backslash, i-separatorLength))
   674                 && !containsAt(line.data, backslash, i-separatorLength))
   729             || containsAt(line, colon, i)) {
   675             || containsAt(line.data, colon, i)) {
   730             length = i;
   676             length = i - line.position;
       
   677             break;
   731             break;
   678         }
   732         }
   679     }
   733     }
   680     if (length > 0) {
   734     if (length > 0) {
   681         QString trimmedGroupsAndName =
   735         QString trimmedGroupsAndName = codec->toUnicode(line.left(length)).trimmed();
   682                 codec->toUnicode(line.data.mid(line.position, length)).trimmed();
       
   683         QStringList parts = trimmedGroupsAndName.split(QLatin1Char('.'));
   736         QStringList parts = trimmedGroupsAndName.split(QLatin1Char('.'));
   684         if (parts.count() > 1) {
   737         if (parts.count() > 1) {
   685             groupsAndName.second = parts.takeLast();
   738             groupsAndName.second = parts.takeLast();
   686             groupsAndName.first = parts;
   739             groupsAndName.first = parts;
   687         } else {
   740         } else {
   688             groupsAndName.second = trimmedGroupsAndName;
   741             groupsAndName.second = trimmedGroupsAndName;
   689         }
   742         }
   690         line.setPosition(length + line.position);
   743         line.chopLeft(length);
   691     }
   744     }
   692 
   745 
   693     return groupsAndName;
   746     return groupsAndName;
   694 }
       
   695 
       
   696 /*!
       
   697  * Extracts the value of the property.
       
   698  * Returns an empty string if the value was not found.
       
   699  *
       
   700  * On entry \a line should point to the value anyway.
       
   701  * On exit \a line should point to newline after the value
       
   702  */
       
   703 QByteArray QVersitReaderPrivate::extractPropertyValue(VersitCursor& line) const
       
   704 {
       
   705     QByteArray value = line.data.mid(line.position, line.selection - line.position);
       
   706 
       
   707     /* Now advance the cursor in all cases. */
       
   708     line.position = line.selection;
       
   709     return value;
       
   710 }
   747 }
   711 
   748 
   712 /*!
   749 /*!
   713  * Extracts the property parameters as a QMultiHash using \a codec to determine the delimiters.
   750  * Extracts the property parameters as a QMultiHash using \a codec to determine the delimiters.
   714  * The parameters without names are added as "TYPE" parameters.
   751  * The parameters without names are added as "TYPE" parameters.
   715  *
   752  *
   716  * On entry \a line should contain the entire line.
   753  * On entry \a line should contain the line sans the group and name
   717  * On exit, line will be updated to point to the start of the value.
   754  * On exit, line will be updated to have the parameters removed.
   718  */
   755  */
   719 QMultiHash<QString,QString> QVersitReaderPrivate::extractVCard21PropertyParams(
   756 QMultiHash<QString,QString> QVersitReaderPrivate::extractVCard21PropertyParams(
   720         VersitCursor& line, QTextCodec *codec) const
   757         LByteArray& line, QTextCodec *codec) const
   721 {
   758 {
   722     QMultiHash<QString,QString> result;
   759     QMultiHash<QString,QString> result;
   723     QList<QByteArray> paramList = extractParams(line, codec);
   760     QList<QByteArray> paramList = extractParams(line, codec);
   724     while (!paramList.isEmpty()) {
   761     while (!paramList.isEmpty()) {
   725         QByteArray param = paramList.takeLast();
   762         QByteArray param = paramList.takeLast();
   732 }
   769 }
   733 
   770 
   734 /*!
   771 /*!
   735  * Extracts the property parameters as a QMultiHash using \a codec to determine the delimiters.
   772  * Extracts the property parameters as a QMultiHash using \a codec to determine the delimiters.
   736  * The parameters without names are added as "TYPE" parameters.
   773  * The parameters without names are added as "TYPE" parameters.
       
   774  *
       
   775  * On entry \a line should contain the line sans the group and name
       
   776  * On exit, line will be updated to have the parameters removed.
   737  */
   777  */
   738 QMultiHash<QString,QString> QVersitReaderPrivate::extractVCard30PropertyParams(
   778 QMultiHash<QString,QString> QVersitReaderPrivate::extractVCard30PropertyParams(
   739         VersitCursor& line, QTextCodec *codec) const
   779         LByteArray& line, QTextCodec *codec) const
   740 {
   780 {
   741     QMultiHash<QString,QString> result;
   781     QMultiHash<QString,QString> result;
   742     QList<QByteArray> paramList = extractParams(line, codec);
   782     QList<QByteArray> paramList = extractParams(line, codec);
   743     while (!paramList.isEmpty()) {
   783     while (!paramList.isEmpty()) {
   744         QByteArray param = paramList.takeLast();
   784         QByteArray param = paramList.takeLast();
   756 
   796 
   757 
   797 
   758 /*!
   798 /*!
   759  * Extracts the parameters as delimited by semicolons using \a codec to determine the delimiters.
   799  * Extracts the parameters as delimited by semicolons using \a codec to determine the delimiters.
   760  *
   800  *
   761  * On entry \a line should point to the start of the parameter section (past the name).
   801  * On entry \a line should contain the content line sans the group and name
   762  * On exit, \a line will be updated to point to the start of the value.
   802  * On exit, \a line will be updated to only have the value remain
   763  */
   803  */
   764 QList<QByteArray> QVersitReaderPrivate::extractParams(VersitCursor& line, QTextCodec *codec) const
   804 QList<QByteArray> QVersitReaderPrivate::extractParams(LByteArray& line, QTextCodec *codec) const
   765 {
   805 {
   766     const QByteArray colon = VersitUtils::encode(':', codec);
   806     const QByteArray colon = VersitUtils::encode(':', codec);
   767     QList<QByteArray> params;
   807     QList<QByteArray> params;
   768 
   808 
   769     /* find the end of the name&params */
   809     /* find the end of the name&params */
   770     int colonIndex = line.data.indexOf(colon, line.position);
   810     int colonIndex = line.indexOf(colon);
   771     if (colonIndex > line.position && colonIndex < line.selection) {
   811     if (colonIndex > 0) {
   772         QByteArray nameAndParamsString = line.data.mid(line.position, colonIndex - line.position);
   812         QByteArray nameAndParamsString = line.left(colonIndex);
   773         params = extractParts(nameAndParamsString, VersitUtils::encode(';', codec), codec);
   813         params = extractParts(nameAndParamsString, VersitUtils::encode(';', codec), codec);
   774 
   814 
   775         /* Update line */
   815         /* Update line */
   776         line.setPosition(colonIndex + colon.length());
   816         line.chopLeft(colonIndex + colon.length());
   777     } else if (colonIndex == line.position) {
   817     } else if (colonIndex == 0) {
   778         // No parameters.. advance past it
   818         // No parameters.. advance past it
   779         line.setPosition(line.position + colon.length());
   819         line.chopLeft(colon.length());
   780     }
   820     }
   781 
   821 
   782     return params;
   822     return params;
   783 }
   823 }
   784 
   824 
   858     }
   898     }
   859 
   899 
   860     return codec->toUnicode(value);
   900     return codec->toUnicode(value);
   861 }
   901 }
   862 
   902 
   863 /*!
   903 /*
   864  * Returns true if and only if \a text contains \a ba at \a index
   904  * Returns true if and only if \a text contains \a ba at \a index
   865  *
   905  *
   866  * On entry, index must be >= 0
   906  * On entry, index must be >= 0
   867  */
   907  *
   868 bool QVersitReaderPrivate::containsAt(const QByteArray& text, const QByteArray& match, int index)
   908  * T is either a QByteArray or LByteArray
       
   909  */
       
   910 template <class T> bool QVersitReaderPrivate::containsAt(const T& text, const QByteArray& match, int index)
   869 {
   911 {
   870     int n = match.length();
   912     int n = match.length();
   871     if (text.length() - index < n)
   913     if (text.size() - index < n)
   872         return false;
   914         return false;
   873     const char* textData = text.constData();
   915     const char* textData = text.constData();
   874     const char* matchData = match.constData();
   916     const char* matchData = match.constData();
   875     return memcmp(textData+index, matchData, n) == 0;
   917     return memcmp(textData+index, matchData, n) == 0;
   876 }
   918 }