QSettings 自定义格式

QSettings 自定义格式(第一部分)

早就注意到 QSettings 提供了自定义配置文件格式的功能,却一直没怎么看。今天用了一天时间,做了个练习,特此记录一下。

  • -- dbzhang800 2010-11-06 23:05:10

拿什么练习呢?

前段时间,有网友抱怨,QSettings 的ini格式中,在section和key中的中文在文件中看到的是乱码(其实是汉字对应的utf16的转义字符)。就以此开始吧:

  • 一个自定义的 .ini 格式的文件
  • 文件中的 section 和 key 都可以直接显示中文
  • section 的 嵌套方式采用 [A/B/C]

  • 尽可能完备一点:QVariant都能正确写入与读出
效果

一个配置文件的例子:

[A/B]
emptystringlist =
stringlist = abc, d ef,

[General]
double = 3.14159
emptystring =
nullstring =
string = hello

[section1]
bytearray = @Variant(\0\0\0\xc\0\0\0\t......121)
point = @Variant(\0\0\0\x19\0\0\0\x64\0\0\0()

[section1/section11/section111]
nested = @Variant(\0\0\0\x15\0\0\0\n\0\0\0\x14), @Variant(\0\0\0\x13\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0)

[你好]
列表 = 中, 国

[你好/汉民]
微测 = 北京

生成该文件的代码(使用起来和标准的 IniFormat 没有区别,自己想换个格式还是蛮方便的):

#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QPoint>
#include "custom.h"

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QSettings::Format format = QSettings::registerFormat("ini", IniReadFunc, IniWriteFunc);
QSettings settings("config.ini", format);

settings.setValue("string", "hello");
settings.setValue("emptystring", "");
settings.setValue("nullstring", QString());
settings.setValue("double", 3.14159);

settings.beginGroup("section1");
settings.setValue("point", QPoint(100, 40));
settings.setValue("bytearray", QByteArray("......121"));
settings.endGroup();

settings.setValue("section1/section11/section111/nested",
QVariantList()<<QSize(10,20)<<QRect(0,0,1,1));

settings.beginGroup("A");
settings.beginGroup("B");
settings.setValue("stringlist", QStringList()<<"abc"<<"d ef"<<"");
settings.setValue("emptystringlist", QStringList());
settings.endGroup();
settings.endGroup();

settings.beginGroup(QString::fromLocal8Bit("你好"));
settings.setValue(QString::fromLocal8Bit("汉民/微测"), QString::fromLocal8Bit("北京"));
settings.setValue(QString::fromLocal8Bit("列表"), QStringList()
<< QString::fromLocal8Bit("中")
<< QString::fromLocal8Bit("国"));
settings.endGroup();

return a.exec();
}如何实现

如何用操作我们自己定义格式的配置文件呢?

从前面的代码也可以看出:注册自定义格式,使用的是 registerFormat 函数:

Format QSettings::registerFormat ( const QString & extension, ReadFunc readFunc, WriteFunc writeFunc, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive ) [static]

该函数的中间两个参数,是两个函数指针。我们的任务就是实现这两个函数:一个负责读文件,一个负责写文件

百度的长度限制,真郁闷:非要分成几篇来写。

完整的代码附在在后面两部分(为了减小长度去掉了语法高亮)

QSettings 自定义格式(第二部分)

前一部分   我们先看一下读与写两个函数

代码(1)

读函数:解析文件,将解析出的内容存放到一个QMap<QString, QVariant>

bool IniReadFunc(QIODevice &device, QSettings::SettingsMap &settingsMap)
{
QString currentSection;
QTextStream stream(&device);
stream.setCodec("UTF-8");
QString data;
bool ok = true;
while (!stream.atEnd()) {
data = stream.readLine();
if (data.trimmed().isEmpty()) {
continue;
}
if (data[0] == QChar('[')) {
QString iniSection;
int inx = data.indexOf(QChar(']'));
if (inx == -1){
ok = false;
iniSection = data.mid(1);
} else {
iniSection = data.mid(1, inx - 1);
}

iniSection = iniSection.trimmed();
if (iniSection.compare(QString("general"), Qt::CaseInsensitive) == 0) {
currentSection.clear();
} else {
if (iniSection.compare(QString("%general"), Qt::CaseInsensitive) == 0) {
currentSection = QString("general");
} else {
currentSection = iniSection;
}
currentSection += QChar('/');
}
} else {
bool inQuotes = false;
int equalsPos = -1;
QList<int> commaPos;
int i = 0;
while (i < data.size())
{
QChar ch = data.at(i);
if (ch == QChar('=')) {
if (!inQuotes && equalsPos == -1) {
equalsPos = i;
}
} else if (ch == QChar('"')) {
inQuotes = !inQuotes;
} else if (ch == QChar(',')) {
if (!inQuotes && equalsPos != -1) {
commaPos.append(i);
}
} else if (ch == QChar(';') || ch == QChar('#')) {
if (!inQuotes) {
data.resize(i);
break;
}
} else if (ch == QChar('\\')) {
if (++i < data.size()) {
} else {
ok = false;
break;
}
}
i++;
}
if (equalsPos == -1) {
break;
} else {
QString key = data.mid(0, equalsPos).trimmed();
if (key.isEmpty()) {
break;
} else {
key = currentSection + key;
}
if (commaPos.isEmpty()) { //value
QString v = data.mid(equalsPos+1).trimmed();
if (v.startsWith("\"") && v.endsWith("\"") && v.length()>1) {
v = v.mid(1, v.length()-2); }
settingsMap[key] = stringToVariant(unescapedString(v));
} else { //value list
commaPos.prepend(equalsPos);
commaPos.append(-1);
QVariantList vals;
for (int i=1; i<commaPos.size(); ++i) {
QString d = data.mid(commaPos.at(i-1)+1, commaPos.at(i)-commaPos.at(i-1)-1);
QString v = d.trimmed();
if (v.startsWith("\"") && v.endsWith("\"") && v.length()>1) {
v = v.mid(1, v.length()-2); }
vals.append(stringToVariant(unescapedString(v)));
}
settingsMap[key] = vals;
}
}
}
}
return ok;
}

写函数:内存中的配置信息QMap<QString, QVariant>写入文件

bool IniWriteFunc(QIODevice &device, const QSettings::SettingsMap &settingsMap)
{
#ifdef Q_OS_WIN
const char * const eol = "\r\n";
#else
const char eol = '\n';
#endif
bool writeError = false;

QString lastSection;
QMapIterator<QString,QVariant> it(settingsMap);
while(it.hasNext() && !writeError) {
it.next();
QString key = it.key();
QString section;
qDebug()<<"key: "<<key;
int idx = key.lastIndexOf(QChar('/'));
if (idx == -1) {
section = QString("[General]");
} else {
section = key.left(idx);
key = key.mid(idx+1);
if (section.compare(QString("General"), Qt::CaseInsensitive) == 0) {
section = QString("[%General]");
} else {
section.prepend(QChar('['));
section.append(QChar(']'));
}
}
if (section.compare(lastSection, Qt::CaseInsensitive))
{
if (!lastSection.isEmpty()) {
device.write(eol);
}
lastSection = section;
if (device.write(section.toUtf8() + eol) == -1) {
writeError = true;
}
}
QByteArray block = key.toUtf8();
block += " = ";
if (it.value().type() == QVariant::StringList) {
foreach (QString s, it.value().toStringList()) {
block += escapedString(s);
block += ", ";
}
if (block.endsWith(", ")) {
block.chop(2);
}
} else if (it.value().type() == QVariant::List) {
foreach (QVariant v, it.value().toList()) {
block += escapedString(variantToString(v));
block += ", ";
}
if (block.endsWith(", ")) {
block.chop(2);
}
} else {
block += escapedString(variantToString(it.value()));
}
block += eol;
if (device.write(block) == -1) {
writeError = true;
}
}
return writeError;
}
百度的长度限制,真郁闷:非要分成几篇来写,而且关键字高亮不加都还超过长度了

这两个函数中,调用了另外4个帮助函数,见下一部分

QSettings 自定义格式(第三部分)

接  第二部分 我们先看一下第二部分调用到的4个函数:

  • 我们将ini文件中的键值读入到 QVariant 中,需要两个步骤:
    • 因为文件内的一些字符被转义了,比如 "\x1234\t\0"等,所以需要 unescape

    • 从 unescape 后的字符串构造出 QVariant
  • 当将QVariant写入文件时:
    • 将 QVariant 转换成字符串
    • 处理字符串中的特殊字符,即 escape

于是 4种操作,就对应了下面4个函数:

将 QVariant 转成字符串

QString variantToString(const QVariant &v)
{
QString result;
switch (v.type()) {
case QVariant::String:
case QVariant::LongLong:
case QVariant::ULongLong:
case QVariant::Int:
case QVariant::UInt:
case QVariant::Bool:
case QVariant::Double:
case QVariant::KeySequence: {
result = v.toString();
if (result.startsWith(QChar('@')))
result.prepend(QChar('@'));
break;
}
default: {
QByteArray a;
{
QDataStream s(&a, QIODevice::WriteOnly);
s.setVersion(QDataStream::Qt_4_0);
s << v;
}

result = QString("@Variant(");
result += QString::fromLatin1(a.constData(), a.size());
result += QChar(')');
break;
}
}

return result;
}从string构造QVariant

QVariant stringToVariant(const QString &s)
{
if (s.startsWith(QChar('@'))) {
if (s.endsWith(QChar(')'))) {
if (s.startsWith(QString("@Variant("))) {
QByteArray a(s.toUtf8().mid(9));
QDataStream stream(&a, QIODevice::ReadOnly);
stream.setVersion(QDataStream::Qt_4_0);
QVariant result;
stream >> result;
return result;
}
}
if (s.startsWith(QString("@@")))
return QVariant(s.mid(1));
}
return QVariant(s);
}将字符串进行转义处理

QByteArray escapedString(const QString &src)
{
bool needsQuotes = false;
bool escapeNextIfDigit = false;
int i;
QByteArray result;
result.reserve(src.size() * 3 / 2);
for (i = 0; i < src.size(); ++i) {
uint ch = src.at(i).unicode();
if (ch == ';' || ch == ',' || ch == '=' || ch == '#') {
needsQuotes = true;
}
if (escapeNextIfDigit && ((ch >= '0' && ch <= '9')
|| (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'))) {
result += "\\x";
result += QByteArray::number(ch, 16);
continue;
}

escapeNextIfDigit = false;

switch (ch) {
case '\0':
result += "\\0";
escapeNextIfDigit = true;
break;
case '\n':
result += "\\n";
break;
case '\r':
result += "\\r";
break;
case '\t':
result += "\\t";
break;
case '"':
case '\\':
result += '\\';
result += (char)ch;
break;
default:
if (ch <= 0x1F) {
result += "\\x";
result += QByteArray::number(ch, 16);
escapeNextIfDigit = true;
} else{
result += QString(src[i]).toUtf8();
}
}
}
if (result.size()>0 && (result.at(0)==' ' || result.at(result.size() - 1) == ' ')) {
needsQuotes = true;
}
if (needsQuotes) {
result.prepend('"');
result.append('"');
}
return result;
}解析转义字符

const char hexDigits[] = "0123456789ABCDEF";

QString unescapedString(const QString &src)
{
static const char escapeCodes[][2] =
{
{ 'a', '\a' },
{ 'b', '\b' },
{ 'f', '\f' },
{ 'n', '\n' },
{ 'r', '\r' },
{ 't', '\t' },
{ 'v', '\v' },
{ '"', '"' },
{ '?', '?' },
{ '\'', '\'' },
{ '\\', '\\' }
};
static const int numEscapeCodes = sizeof(escapeCodes) / sizeof(escapeCodes[0]);

QString stringResult;
int escapeVal = 0;
QChar ch;
int i = 0;
normal:
while (i < src.size()) {
ch = src.at(i);
if (ch == QChar('\\')) {
++i;
if (i >= src.size()) {
break;
}
ch = src.at(i++);
for (int j = 0; j < numEscapeCodes; ++j) {
if (ch == escapeCodes[j][0]) {
stringResult += QChar(escapeCodes[j][1]);
goto normal;
}
}
if (ch == 'x') {
escapeVal = 0;
if (i >= src.size())
break;
ch = src.at(i);
if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'))
goto hexEscape;
} else if (ch >= '0' && ch <= '7') {
escapeVal = ch.unicode() - '0';
goto octEscape;
} else {
//skip
}
} else {
stringResult += ch;
}
i++;
}
goto end;

hexEscape:
if (i >= src.size()) {
stringResult += QChar(escapeVal);
goto end;
}

ch = src.at(i);
if (ch >= 'a')
ch = ch.unicode() - ('a' - 'A');
if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')) {
escapeVal <<= 4;
escapeVal += strchr(hexDigits, ch.toLatin1()) - hexDigits;
++i;
goto hexEscape;
} else {
stringResult += QChar(escapeVal);
goto normal;
}

octEscape:
if (i >= src.size()) {
stringResult += QChar(escapeVal);
goto end;
}

ch = src.at(i);
if (ch >= '0' && ch <= '7') {
escapeVal <<= 3;
escapeVal += ch.toLatin1() - '0';
++i;
goto octEscape;
} else {
stringResult += QChar(escapeVal);
goto normal;
}

end:
return stringResult;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值