Skip to content

Instantly share code, notes, and snippets.

@ialexpovad
Forked from ianmac45/qsettings-xml.hpp
Created June 9, 2023 19:21

Revisions

  1. @ianmac45 ianmac45 revised this gist Mar 15, 2013. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions qsettings-xml.hpp
    Original file line number Diff line number Diff line change
    @@ -113,8 +113,6 @@ bool readSettingsXml(QIODevice &device, QMap<QString, QVariant> &map)

    //if it gets here, an error occured.
    map.clear();
    qWarning() << "An error occured while laoding the settings. All settings have been reset.";

    return false;
    }

  2. @ianmac45 ianmac45 revised this gist Mar 15, 2013. 1 changed file with 3 additions and 5 deletions.
    8 changes: 3 additions & 5 deletions qsettings-xml.hpp
    Original file line number Diff line number Diff line change
    @@ -128,10 +128,8 @@ bool writeSettingsXml(QIODevice &device, const QMap<QString, QVariant> &map)
    //of this step is to put all the keys of one category next to each other.
    //but we do not sort within the category. in this step, we place our results
    //from the QStringList of QMap.keys() into a tree-like structure
    QStringList keys = map.keys();
    for(int k = 0; k < keys.size(); k++)
    foreach(const QString &unsplitKey, map.keys())
    {
    QString unsplitKey = keys[k];
    QStringList segs = unsplitKey.split("/", QString::SkipEmptyParts);
    QString val = map[unsplitKey].toString();

    @@ -155,9 +153,9 @@ bool writeSettingsXml(QIODevice &device, const QMap<QString, QVariant> &map)
    //child of the current node if it doesn't exist. then we use it
    //for the next iteration
    XmlNode *foundItem = 0;
    for(int j = 0; j < cur->children().size(); j++)
    foreach(QObject *object, cur->children())
    {
    XmlNode *child = (XmlNode*) cur->children()[j];
    XmlNode *child = (XmlNode*) object;
    if(0 == QString::compare(child->tagName, segs[i], Qt::CaseInsensitive))
    {
    foundItem = child;
  3. @ianmac45 ianmac45 revised this gist Mar 15, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion qsettings-xml.hpp
    Original file line number Diff line number Diff line change
    @@ -100,7 +100,7 @@ bool readSettingsXml(QIODevice &device, QMap<QString, QVariant> &map)
    //otherwise, we just closed the current category.
    //on the next loop iteration, we should get either the start of the next category or the closing tag of the parent (either the parent category or the "parent" leaf name)
    else
    curNode = (XmlNode*) QScopedPointer<XmlNode>(curNode)->parent();
    curNode = (XmlNode*) QScopedPointer<XmlNode>(curNode)->parent();

    break;

  4. @ianmac45 ianmac45 revised this gist Mar 15, 2013. 1 changed file with 7 additions and 8 deletions.
    15 changes: 7 additions & 8 deletions qsettings-xml.hpp
    Original file line number Diff line number Diff line change
    @@ -77,15 +77,14 @@ bool readSettingsXml(QIODevice &device, QMap<QString, QVariant> &map)
    switch(xml.readNext())
    {
    case QXmlStreamReader::StartElement:
    if(curNode == 0)
    {
    if(xml.name().toString() == rootName)
    curNode = new XmlNode(rootName); //root node
    else
    return false; // invalid format: first element *must* be root tag
    }
    else
    if(curNode != 0)
    //we're already processing the file if there already is a current node
    curNode = new XmlNode(xml.name().toString(), QString(), curNode);
    else if(xml.name().toString() == rootName)
    //no current node? this must be the first one: the root
    curNode = new XmlNode(rootName);
    else
    return false; // invalid format: first element *must* be root tag

    break;

  5. @ianmac45 ianmac45 revised this gist Mar 15, 2013. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions qsettings-xml.hpp
    Original file line number Diff line number Diff line change
    @@ -130,9 +130,9 @@ bool writeSettingsXml(QIODevice &device, const QMap<QString, QVariant> &map)
    //but we do not sort within the category. in this step, we place our results
    //from the QStringList of QMap.keys() into a tree-like structure
    QStringList keys = map.keys();
    for(int i = 0; i < keys.size(); i++)
    for(int k = 0; k < keys.size(); k++)
    {
    QString unsplitKey = keys[i];
    QString unsplitKey = keys[k];
    QStringList segs = unsplitKey.split("/", QString::SkipEmptyParts);
    QString val = map[unsplitKey].toString();

  6. @ianmac45 ianmac45 created this gist Mar 15, 2013.
    235 changes: 235 additions & 0 deletions qsettings-xml.hpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,235 @@
    #pragma once
    //TODO include appropriate headers
    #include <QtCore/QtCore>
    #include <QtXml/QtXml>

    bool readSettingsXml(QIODevice &device, QMap<QString, QVariant> &map);
    bool writeSettingsXml(QIODevice &device, const QMap<QString, QVariant> &map);

    static const QSettings::Format xmlFormat = QSettings::registerFormat("xml", &readSettingsXml, &writeSettingsXml);
    static const QString rootName = "config";




    //the following code is all used to save QSettings in xml. reading is accomplished
    //through a stream-based xml parser. writing uses a stack-based approach to get
    //all leaves of the tree-like QSettings structure right next to each other in the resulting xml file. writing example:
    /* we want: (which is our current result)
    <root>
    <category1>
    <entry1 />
    <entry2 />
    </category1>
    <category2 />
    </root>
    what we don't want:
    <root>
    <category1>
    <entry1 />
    </category1>
    <categrory2 />
    <category1>
    <entry2 />
    </category1>
    </root>
    */




    //helper class for saving QSettings as xml,
    //used for both reading & writing.
    //children are used only for writing.
    //parent used only for reading.
    class XmlNode : public QObject
    {
    public:
    QString tagName, subtext;

    XmlNode(const QString &name, const QString &text = QString(), QObject *parent = 0) :
    QObject(parent),
    tagName(name),
    subtext(text)
    {
    }

    QString fullPath() const
    {
    const XmlNode *cur = this;
    QString path = tagName;

    while((cur = (const XmlNode *) cur->parent()) != 0)
    path.prepend(cur->tagName + "\\");

    return path.mid(rootName.size() + 1); // remove root node & trailing slash
    }
    };

    bool readSettingsXml(QIODevice &device, QMap<QString, QVariant> &map)
    {
    QXmlStreamReader xml(&device);
    XmlNode *curNode = 0;

    while(!xml.atEnd())
    {
    switch(xml.readNext())
    {
    case QXmlStreamReader::StartElement:
    if(curNode == 0)
    {
    if(xml.name().toString() == rootName)
    curNode = new XmlNode(rootName); //root node
    else
    return false; // invalid format: first element *must* be root tag
    }
    else
    curNode = new XmlNode(xml.name().toString(), QString(), curNode);

    break;

    case QXmlStreamReader::EndElement:
    //if current node has no parent, that means we just closed the root tag
    //we're done!
    if(!curNode->parent())
    {
    delete curNode;
    return true;
    }

    //otherwise, we just closed the current category.
    //on the next loop iteration, we should get either the start of the next category or the closing tag of the parent (either the parent category or the "parent" leaf name)
    else
    curNode = (XmlNode*) QScopedPointer<XmlNode>(curNode)->parent();

    break;

    case QXmlStreamReader::Characters:
    if(!xml.isWhitespace())
    map[curNode->fullPath()] = xml.text().toString();
    break;
    }
    }

    //if it gets here, an error occured.
    map.clear();
    qWarning() << "An error occured while laoding the settings. All settings have been reset.";

    return false;
    }

    bool writeSettingsXml(QIODevice &device, const QMap<QString, QVariant> &map)
    {
    XmlNode *root = new XmlNode(rootName);


    /************************************************************/
    //step 1/2: process the structure of the settings map & the keys. the purpose
    //of this step is to put all the keys of one category next to each other.
    //but we do not sort within the category. in this step, we place our results
    //from the QStringList of QMap.keys() into a tree-like structure
    QStringList keys = map.keys();
    for(int i = 0; i < keys.size(); i++)
    {
    QString unsplitKey = keys[i];
    QStringList segs = unsplitKey.split("/", QString::SkipEmptyParts);
    QString val = map[unsplitKey].toString();

    XmlNode *cur = root;

    //for each segment of the current key, we loop through the branches of
    //our tree looking for appropriate branches/leaves. on the way down to
    //the specific leaf we want, we create & add nodes as needed.

    for(int i = 0; i < segs.length(); i++)
    {
    if(i == segs.length() - 1)
    {
    //the last segment is a leaf that wasn't previously found.
    //we don't keep the ref since it becomes a child of the parent
    new XmlNode(segs[i], val, cur);
    }
    else
    {
    //search for the node for the current segment. create it as a
    //child of the current node if it doesn't exist. then we use it
    //for the next iteration
    XmlNode *foundItem = 0;
    for(int j = 0; j < cur->children().size(); j++)
    {
    XmlNode *child = (XmlNode*) cur->children()[j];
    if(0 == QString::compare(child->tagName, segs[i], Qt::CaseInsensitive))
    {
    foundItem = child;
    break;
    }
    }

    if(!foundItem)
    foundItem = new XmlNode(segs[i], QString(), cur);

    cur = foundItem;
    }
    }
    }





    /************************************************************/
    //step 2/2: write processed data to xml file
    //use a stack to implement writing the tree while converting it to a list.
    //here's the general process:
    // 1) the stack is actually used to traverse the levels of the tree.
    // loop through all entries in the stack while closing all tags.
    // when we find the sentinel '0': if the stack is emtpy, we're done!
    // otherwise, the children of one category are completed.
    // 2) we write the start tag for the current item. we always push the sentinel
    // disregarding whether or not there are children.
    // 3) if there are no children, we simply write the contents and continue.
    // because of the sentinel in the stack, the next iteration of the main
    // loop will close the tag. but, if there are children, we push them all
    // to the stack to output them before closing the parent tag. the subsequent
    // iterations of the main loop will handle all children and/or contents

    QXmlStreamWriter xml(&device);
    xml.setAutoFormatting(true);
    xml.setAutoFormattingIndent(-1);
    xml.writeStartDocument();

    QList<XmlNode*> stack;
    stack << root;

    while(true)
    {
    //see step 1
    XmlNode *cur;
    while((cur = stack.takeLast()) == 0)
    {
    xml.writeEndElement();

    if(stack.isEmpty())
    {
    xml.writeEndDocument();
    delete root;
    return true;
    }
    }

    //see step 2
    xml.writeStartElement(cur->tagName);
    stack << 0; // required to close text-only elements as well as for nodes with children to go back up a level when children are processed.

    //see step 3
    if(cur->children().size() == 0)
    xml.writeCharacters(cur->subtext);
    else
    for(int i = 0; i < cur->children().length(); i++)
    stack << (XmlNode*) cur->children()[i];
    }

    //should *never* get here
    return false;
    }