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