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 /*! |
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 { |
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")); |
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(); |
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¶ms */ |
809 /* find the end of the name¶ms */ |
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 |