QSettings 自定义格式

转自:http://hi.baidu.com/cyclone/blog/item/1bcb5edfed60d20348540320.html


早就注意到 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]

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


代码(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; 
}

  • 我们将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;
}




  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值