Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1695853
  • 博文数量: 584
  • 博客积分: 13857
  • 博客等级: 上将
  • 技术积分: 11883
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-16 09:34

分类: WINDOWS

2012-02-20 15:22:14

转自: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都能正确写入与读出
效果

一个配置文件的例子:


  1. [A/B]
  2. emptystringlist =
  3. stringlist = abc, d ef,

  4. [General]
  5. double = 3.14159
  6. emptystring =
  7. nullstring =
  8. string = hello

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

  12. [section1/section11/section111]
  13. 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)

  14. [你好]
  15. 列表 =,

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

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


  1. #include <QtCore/QCoreApplication>
  2. #include <QtCore/QDebug>
  3. #include <QtCore/QPoint>
  4. #include "custom.h"

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

  10.     settings.setValue("string", "hello");
  11.     settings.setValue("emptystring", "");
  12.     settings.setValue("nullstring", QString());
  13.     settings.setValue("double", 3.14159);

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

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

  20.     settings.beginGroup("A");
  21.     settings.beginGroup("B");
  22.     settings.setValue("stringlist", QStringList()<<"abc"<<"d ef"<<"");
  23.     settings.setValue("emptystringlist", QStringList());
  24.     settings.endGroup();
  25.     settings.endGroup();

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

  32.     return a.exec();
  33. }

如何实现

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

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

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

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


代码(1)

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


  1. bool IniReadFunc(QIODevice &device, QSettings::SettingsMap &settingsMap)
  2. {
  3.     QString currentSection;
  4.     QTextStream stream(&device);
  5.     stream.setCodec("UTF-8");
  6.     QString data;
  7.     bool ok = true;
  8.     while (!stream.atEnd()) {
  9.         data = stream.readLine();
  10.         if (data.trimmed().isEmpty()) {
  11.             continue;
  12.         }
  13.         if (data[0] == QChar('[')) {
  14.             QString iniSection;
  15.             int inx = data.indexOf(QChar(']'));
  16.             if (inx == -1){
  17.                 ok = false;
  18.                 iniSection = data.mid(1);
  19.             } else {
  20.                 iniSection = data.mid(1, inx - 1);
  21.             }

  22.             iniSection = iniSection.trimmed();
  23.             if (iniSection.compare(QString("general"), Qt::CaseInsensitive) == 0) {
  24.                 currentSection.clear();
  25.             } else {
  26.                 if (iniSection.compare(QString("%general"), Qt::CaseInsensitive) == 0) {
  27.                     currentSection = QString("general");
  28.                 } else {
  29.                     currentSection = iniSection;
  30.                 }
  31.                 currentSection += QChar('/');
  32.             }
  33.         } else {
  34.             bool inQuotes = false;
  35.             int equalsPos = -1;
  36.             QList<int> commaPos;
  37.              int i = 0;
  38.             while (i < data.size())
  39.             {
  40.                 QChar ch = data.at(i);
  41.                 if (ch == QChar('=')) {
  42.                     if (!inQuotes && equalsPos == -1) {
  43.                         equalsPos = i;
  44.                     }
  45.                 } else if (ch == QChar('"')) {
  46.                     inQuotes = !inQuotes;
  47.                 } else if (ch == QChar(',')) {
  48.                     if (!inQuotes && equalsPos != -1) {
  49.                         commaPos.append(i);
  50.                     }
  51.                 } else if (ch == QChar(';') || ch == QChar('#')) {
  52.                     if (!inQuotes) {
  53.                         data.resize(i);
  54.                         break;
  55.                     }
  56.                 } else if (ch == QChar('\\')) {
  57.                     if (++i < data.size()) {
  58.                      } else {
  59.                         ok = false;
  60.                         break;
  61.                     }
  62.                 }
  63.                 i++;
  64.             }
  65.              if (equalsPos == -1) {
  66.                 break;
  67.             } else {
  68.                 QString key = data.mid(0, equalsPos).trimmed();
  69.                 if (key.isEmpty()) {
  70.                     break;
  71.                 } else {
  72.                     key = currentSection + key;
  73.                 }
  74.                 if (commaPos.isEmpty()) { //value
  75.                     QString v = data.mid(equalsPos+1).trimmed();
  76.                     if (v.startsWith("\"") && v.endsWith("\"") && v.length()>1) {
  77.                         v = v.mid(1, v.length()-2); }
  78.                     settingsMap[key] = stringToVariant(unescapedString(v));
  79.                 } else { //value list
  80.                     commaPos.prepend(equalsPos);
  81.                     commaPos.append(-1);
  82.                     QVariantList vals;
  83.                     for (int i=1; i
  84.                         QString d = data.mid(commaPos.at(i-1)+1, commaPos.at(i)-commaPos.at(i-1)-1);
  85.                         QString v = d.trimmed();
  86.                         if (v.startsWith("\"") && v.endsWith("\"

写函数:内存中的配置信息QMap写入文件


  1. bool IniWriteFunc(QIODevice &device, const QSettings::SettingsMap &settingsMap)
  2. {
  3. #ifdef Q_OS_WIN
  4.     const char * const eol = "\r\n";
  5. #else
  6.     const char eol = '\n';
  7. #endif
  8.     bool writeError = false;
  9.     
  10.     QString lastSection;
  11.     QMapIterator<QString,QVariant> it(settingsMap);
  12.     while(it.hasNext() && !writeError) {
  13.       it.next();
  14.       QString key = it.key();
  15.       QString section;
  16.       qDebug()<<"key: "<<key;
  17.       int idx = key.lastIndexOf(QChar('/'));
  18.       if (idx == -1) {
  19.           section = QString("[General]");
  20.       } else {
  21.           section = key.left(idx);
  22.           key = key.mid(idx+1);
  23.           if (section.compare(QString("General"), Qt::CaseInsensitive) == 0) {
  24.               section = QString("[%General]");
  25.           } else {
  26.               section.prepend(QChar('['));
  27.               section.append(QChar(']'));
  28.           }
  29.       }
  30.       if (section.compare(lastSection, Qt::CaseInsensitive))
  31.       {
  32.           if (!lastSection.isEmpty()) {
  33.               device.write(eol);
  34.           }
  35.           lastSection = section;
  36.           if (device.write(section.toUtf8() + eol) == -1) {
  37.               writeError = true;
  38.           }
  39.       }
  40.        QByteArray block = key.toUtf8();
  41.       block += " = ";
  42.       if (it.value().type() == QVariant::StringList) {
  43.           foreach (QString s, it.value().toStringList()) {
  44.               block += escapedString(s);
  45.               block += ", ";
  46.           }
  47.           if (block.endsWith(", ")) {
  48.               block.chop(2);
  49.           }
  50.        } else if (it.value().type() == QVariant::List) {
  51.           foreach (QVariant v, it.value().toList()) {
  52.               block += escapedString(variantToString(v));
  53.               block += ", ";
  54.           }
  55.           if (block.endsWith(", ")) {
  56.               block.chop(2);
  57.           }
  58.       } else {
  59.           block += escapedString(variantToString(it.value()));
  60.       }
  61.       block += eol;
  62.       if (device.write(block) == -1) {
  63.           writeError = true;
  64.       }
  65.      }
  66.     return writeError;
  67. }
  • 我们将ini文件中的键值读入到 QVariant 中,需要两个步骤:
    • 因为文件内的一些字符被转义了,比如 "\x1234\t\0"等,所以需要 unescape

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

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

将 QVariant 转成字符串

  1. QString variantToString(const QVariant &v)
  2. {
  3.     QString result;
  4.     switch (v.type()) {
  5.         case QVariant::String:
  6.         case QVariant::LongLong:
  7.         case QVariant::ULongLong:
  8.         case QVariant::Int:
  9.         case QVariant::UInt:
  10.         case QVariant::Bool:
  11.         case QVariant::Double:
  12.         case QVariant::KeySequence: {
  13.             result = v.toString();
  14.             if (result.startsWith(QChar('@')))
  15.                 result.prepend(QChar('@'));
  16.             break;
  17.         }
  18.         default: {
  19.             QByteArray a;
  20.             {
  21.                 QDataStream s(&a, QIODevice::WriteOnly);
  22.                 s.setVersion(QDataStream::Qt_4_0);
  23.                 s << v;
  24.             }

  25.             result = QString("@Variant(");
  26.             result += QString::fromLatin1(a.constData(), a.size());
  27.             result += QChar(')');
  28.             break;
  29.         }
  30.     }

  31.     return result;
  32. }
从string构造QVariant
  1. QVariant stringToVariant(const QString &s)
  2. {
  3.     if (s.startsWith(QChar('@'))) {
  4.         if (s.endsWith(QChar(')'))) {
  5.             if (s.startsWith(QString("@Variant("))) {
  6.                 QByteArray a(s.toUtf8().mid(9));
  7.                 QDataStream stream(&a, QIODevice::ReadOnly);
  8.                 stream.setVersion(QDataStream::Qt_4_0);
  9.                 QVariant result;
  10.                 stream >> result;
  11.                 return result;
  12.             }
  13.         }
  14.         if (s.startsWith(QString("@@")))
  15.             return QVariant(s.mid(1));
  16.     }
  17.     return QVariant(s);
  18. }

将字符串进行转义处理

  1. QByteArray escapedString(const QString &src)
  2. {
  3.     bool needsQuotes = false;
  4.     bool escapeNextIfDigit = false;
  5.     int i;
  6.     QByteArray result;
  7.     result.reserve(src.size() * 3 / 2);
  8.     for (i = 0; i < src.size(); ++i) {
  9.         uint ch = src.at(i).unicode();
  10.         if (ch == ';' || ch == ',' || ch == '=' || ch == '#') {
  11.             needsQuotes = true;
  12.         }
  13.         if (escapeNextIfDigit && ((ch >= '0' && ch <= '9')
  14.                  || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'))) {
  15.             result += "\\x";
  16.             result += QByteArray::number(ch, 16);
  17.             continue;
  18.         }
  19.         escapeNextIfDigit = false;
  20.         switch (ch) {
  21.         case '\0':
  22.             result += "\\0";
  23.             escapeNextIfDigit = true;
  24.             break;
  25.         case '\n':
  26.             result += "\\n";
  27.             break;
  28.         case '\r':
  29.             result += "\\r";
  30.             break;
  31.         case '\t':
  32.             result += "\\t";
  33.             break;
  34.         case '"':
  35.         case '\\':
  36.             result += '\\';
  37.             result += (char)ch;
  38.             break;
  39.         default:
  40.             if (ch <= 0x1F) {
  41.                 result += "\\x";
  42.                 result += QByteArray::number(ch, 16);
  43.                 escapeNextIfDigit = true;
  44.             } else{
  45.                 result += QString(src[i]).toUtf8();
  46.             }
  47.         }
  48.     }
  49.     if (result.size()>0 && (result.at(0)==' ' || result.at(result.size() - 1) == ' ')) {
  50.         needsQuotes = true;
  51.     }
  52.     if (needsQuotes) {
  53.         result.prepend('"');
  54.         result.append('"');
        }
        return result;
    }

解析转义字符

  1. const char hexDigits[] = "0123456789ABCDEF";

  2. QString unescapedString(const QString &src)
  3. {
  4.     static const char escapeCodes[][2] =
  5.     {
  6.         { 'a', '\a' },
  7.         { 'b', '\b' },
  8.         { 'f', '\f' },
  9.         { 'n', '\n' },
  10.         { 'r', '\r' },
  11.         { 't', '\t' },
  12.         { 'v', '\v' },
  13.         { '"', '"' },
  14.         { '?', '?' },
  15.         { '\'', '\'' },
  16.         { '\\', '\\' }
  17.     };
  18.     static const int numEscapeCodes = sizeof(escapeCodes) / sizeof(escapeCodes[0]);

  19.     QString stringResult;
  20.     int escapeVal = 0;
  21.     QChar ch;
  22.     int i = 0;
  23. normal:
  24.     while (i < src.size()) {
  25.         ch = src.at(i);
  26.         if (ch == QChar('\\')) {
  27.             ++i;
  28.             if (i >= src.size()) {
  29.                 break;
  30.             }
  31.             ch = src.at(i++);
  32.             for (int j = 0; j < numEscapeCodes; ++j) {
  33.                 if (ch == escapeCodes[j][0]) {
  34.                     stringResult += QChar(escapeCodes[j][1]);
  35.                     goto normal;
  36.                 }
  37.             }
  38.             if (ch == 'x') {
  39.                 escapeVal = 0;
  40.                 if (i >= src.size())
  41.                     break;
  42.                 ch = src.at(i);
  43.                 if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'))
  44.                     goto hexEscape;
  45.             } else if (ch >= '0' && ch <= '7') {
  46.                 escapeVal = ch.unicode() - '0';
  47.                 goto octEscape;
  48.             } else {
  49.                 //skip
  50.             }
  51.         } else {
  52.             stringResult += ch;
  53.         }
  54.                 i++;
  55.     }
  56.     goto end;

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

  62.     ch = src.at(i);
  63.     if (ch >= 'a')
  64.         ch = ch.unicode() - ('a' - 'A');
  65.     if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')) {
  66.         escapeVal <<= 4;
  67.         escapeVal += strchr(hexDigits, ch.toLatin1()) - hexDigits;
  68.         ++i;
  69.         goto hexEscape;
  70.     } else {
  71.         stringResult += QChar(escapeVal);
  72.         goto normal;
  73.     }

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

  79.     ch = src.at(i);
  80.     if (ch >= '0' && ch <= '7') {
  81.         escapeVal <<= 3;
  82.         escapeVal += ch.toLatin1() - '0';
  83.         ++i;
  84.         goto octEscape;
  85.     } else {
  86.         stringResult += QChar(escapeVal);
  87.         goto normal;
  88.     }

  89. end:
  90.     return stringResult;
  91. }




阅读(2292) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~