[NEWBIE] Parsing XML to strings



  • Yeah, as the title suggests, I'm trying to parse XML to strings. Please keep in mind that I'm a noob in Qt right now, and I've been coding in the dark. (Metaphorically, not literally.) I don't want it put into a visual tree or anything, I want to be able to work with the strings taken from an XML document for further use within my program. So far, I've written this:
    Source file:
    @#include "xmlinterpret.h"
    #include <QtXml>
    #include <QtXml/QXmlStreamReader>
    #include <QIODevice>
    #include <QString>
    #include <QDomDocument>
    #include <QFile>

    QString gameName = "Title: ";
    QString globalObjectsFile;
    QString globalActorsFile;
    QString globalTerrainFile;
    QString globalMusicFile;
    QString globalSoundsFile;
    QString globalItemsFile;

    void LaunchPad::parseXmlFile() {
    QFile xmlFile("zgame.xml");
    if (!xmlFile.open(QIODevice::ReadOnly | QIODevice::Text))
    return;

    QString xmlFileText;
    QTextStream xmlFileStream(&xmlFile);
    while (!xmlFileStream.atEnd()) {
        xmlFileText = xmlFileStream.readAll();
    }
    QXmlStreamReader xml(xmlFileText);
    
    while(!xml.atEnd()) {
        QXmlStreamReader::TokenType token = xml.readNext();
        if(token == QXmlStreamReader::StartDocument) {
            if (token == QXmlStreamReader::StartElement) {
                if(xml.name() == "zgame") {
                    continue;
                }
                if (xml.name() == "title") {
                    gameName.append(this->parseTitle(xml));
                }
                if(xml.name() == "index") {
                    continue;
                }
                if(xml.name() == "objects") {
                    globalObjectsFile.append(this->parseIndexFiles(xml));
                }
                if (xml.name() == "actors") {
                    globalActorsFile.append(this->parseIndexFiles(xml));
                }
                if (xml.name() == "terrains") {
    
                    globalTerrainFile.append(this->parseIndexFiles(xml));
                }
                if (xml.name() == "music") {
                    globalMusicFile.append(this->parseIndexFiles(xml));
                }
                if (xml.name() == "sounds") {
                    globalSoundsFile.append(this->parseIndexFiles(xml));
                }
                if (xml.name() == "items") {
                    globalItemsFile.append(this->parseIndexFiles(xml));
                }
            }
        }
    }
    xml.clear();
    

    }

    void LaunchPad::parseTitle(QXmlStreamReader& xml) {
    if (xml.tokenType() != QXmlStreamReader::StartElement && xml.name() == "gamename") {
    return title;
    }
    while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == "gamename")) {
    if(xml.tokenType() == QXmlStreamReader::StartElement) {
    QString gamePrefix = xml.name().toString();
    gameName.append(gamePrefix);
    }
    }
    }

    void LaunchPad::parseIndexFiles(QXmlStreamReader& xml) {

    }

    void LaunchPad::buildIndex() {
    //Object index
    QFile globalObjectsXmlFile(globalObjectsFile);
    globalObjectsXmlFile.open(QIODevice::readAll());
    QByteArray globalObjectsData(globalObjectsXmlFile);
    objectIndex.setContent(globalObjectsData);

    //Actor index
    QFile globalActorsXmlFile&#40;globalActorsFile&#41;;
    globalActorsXmlFile.open(QIODevice::ReadOnly);
    QByteArray globalActorsData(QFile::readAll(globalActorsXmlFile));
    actorIndex.setContent(globalActorsData);
    
    //Terrain index
    QFile globalTerrainXmlFile&#40;globalTerrainFile&#41;;
    globalTerrainXmlFile.open(QIODevice::ReadOnly);
    QByteArray globalTerrainData(QFile::readAll(globalTerrainXmlFile));
    terrainIndex.setContent(globalTerrainData);
    
    //Music index
    QFile globalMusicFileXmlFile&#40;globalMusicFile&#41;;
    globalMusicFileXmlFile.open(QIODevice::ReadOnly);
    QByteArray globalMusicData(QFile::readAll(globalMusicFileXmlFile));
    musicIndex.setContent(globalMusicData);
    
    //Sound index
    QFile globalSoundsXmlFile&#40;globalSoundsFile&#41;;
    globalSoundsXmlFile.open(QIODevice::ReadOnly);
    QByteArray globalSoundsData(QFile::readAll(globalSoundsXmlFile));
    soundIndex.setContent(globalSoundsData);
    
    //Item index
    QFile globalItemsXmlFile&#40;globalItemsFile&#41;;
    globalItemsXmlFile.open(QIODevice::ReadOnly);
    QByteArray globalItemsData(QFile::readAll(globalItemsXmlFile));
    itemIndex.setContent(globalItemsData);
    

    }
    @
    Header:
    @#ifndef XMLINTERPRET_H
    #define XMLINTERPRET_H

    #include <QtXml/QXmlStreamReader>

    #include <QObject>
    #include <QWidget>
    #include <QDomDocument>

    class LaunchPad : public QObject {
    Q_OBJECT

    public:
    explicit LaunchPad(QObject *parent = 0);
    ~LaunchPad();

    signals:

    public slots:

    private slots:
    void parseXmlFile();
    void parseIndexFiles();
    void parseTitle();
    void buildIndex();
    void parseObject();
    void parseActors();
    void parseTerrain();
    void parseMusic();
    void parseSounds();
    void parseItems();

    private:

    QDomDocument objectIndex;
    QDomDocument actorIndex;
    QDomDocument terrainIndex;
    QDomDocument musicIndex;
    QDomDocument soundIndex;
    QDomDocument itemIndex;
    

    };

    #endif // XMLINTERPRET_H
    @
    What I'm trying to do is this: The initial xml file (zgame.xml) contains the names of other Xml files and also a title. These other Xml files contain an index of objects with multiple child values that I want to put in a non-visual DOM-tree so that they can be imported by other parts of the program. What I've tried to do is parse the text from the initial Xml file, assign it to QStrings, and use them as file location for QIODevices.
    The main problem I have is that I don't understand how QXmlStreamReader works... When I use all those ifs and that stuff, does it forward the string or a piece of the Xml to be parsed?
    All the examples I've seen haven't described this way of working with the Xml, but rather just putting it into some visual component like a tree of nodes.
    I'd really appreciate some help on this.



  • Hi HowyHappy,

    bq. The main problem I have is that I don’t understand how QXmlStreamReader works… When I use all those ifs and that stuff, does it forward the string or a piece of the Xml to be parsed?
    All the examples I’ve seen haven’t described this way of working with the Xml, but rather just putting it into some visual component like a tree of nodes.

    the difference between the examples and your code then is only the target, where the strings go to. So from XML parsing and reading, it should be the same.

    As you post here, it seems not to work, so what do you see? did you debug it?

    I saw one bug:

    @
    while(!xml.atEnd()) {
    QXmlStreamReader::TokenType token = xml.readNext();
    if(token == QXmlStreamReader::StartDocument) {
    if (token == QXmlStreamReader::StartElement) {
    @

    you only check for a StartElement if it's also the StartDocument, all otrher iterations are ignored?



  • I get an error that reoccurs for all the instances I've used the other functions:
    @../xml-interpret/xmlinterpret.cpp:39:57: error: no matching function for call to ‘LaunchPad::parseTitle(QXmlStreamReader&)’@
    And also a "candidate is: void::parseTitle()"
    I also get:
    @../xml-interpret/xmlinterpret.cpp:68:6: error: prototype for ‘void LaunchPad::parseTitle(QXmlStreamReader&)’ does not match any in class ‘LaunchPad’@
    have I forgotten to add something when declaring the function?
    I also have this ginormous error about QByteArray:
    @../xml-interpret/xmlinterpret.cpp:88:50: error: cannot call member function ‘QByteArray QIODevice::readAll()’ without object
    ../xml-interpret/xmlinterpret.cpp:89:54: error: no matching function for call to ‘QByteArray::QByteArray(QFile&)’
    ../../../QtSDK11/Desktop/Qt/473/gcc/include/QtCore/qbytearray.h:369:5: note: candidates are: QByteArray::QByteArray(QByteArray::Data*, int, int)
    ../../../QtSDK11/Desktop/Qt/473/gcc/include/QtCore/qbytearray.h:421:8: note: QByteArray::QByteArray(const QByteArray&)
    ../../../QtSDK11/Desktop/Qt/473/gcc/include/QtCore/qbytearray.h:141:5: note: QByteArray::QByteArray(int, Qt::Initialization)
    ../../../QtSDK11/Desktop/Qt/473/gcc/include/QtCore/qbytearray.h:140:5: note: QByteArray::QByteArray(int, char)
    ../../../QtSDK11/Desktop/Qt/473/gcc/include/QtCore/qbytearray.h:139:5: note: QByteArray::QByteArray(const char*, int)
    ../../../QtSDK11/Desktop/Qt/473/gcc/include/QtCore/qbytearray.h:138:5: note: QByteArray::QByteArray(const char*)
    ../../../QtSDK11/Desktop/Qt/473/gcc/include/QtCore/qbytearray.h:382:8: note: QByteArray::QByteArray()
    ../xml-interpret/xmlinterpret.cpp:95:67: error: no matching function for call to ‘QIODevice::readAll(QFile&)’
    ../../../QtSDK11/Desktop/Qt/473/gcc/include/QtCore/qiodevice.h:119:16: note: candidate is: QByteArray QIODevice::readAll()
    ../xml-interpret/xmlinterpret.cpp:101:69: error: no matching function for call to ‘QIODevice::readAll(QFile&)’
    ../../../QtSDK11/Desktop/Qt/473/gcc/include/QtCore/qiodevice.h:119:16: note: candidate is: QByteArray QIODevice::readAll()
    ../xml-interpret/xmlinterpret.cpp:107:69: error: no matching function for call to ‘QIODevice::readAll(QFile&)’
    ../../../QtSDK11/Desktop/Qt/473/gcc/include/QtCore/qiodevice.h:119:16: note: candidate is: QByteArray QIODevice::readAll()
    ../xml-interpret/xmlinterpret.cpp:113:67: error: no matching function for call to ‘QIODevice::readAll(QFile&)’
    ../../../QtSDK11/Desktop/Qt/473/gcc/include/QtCore/qiodevice.h:119:16: note: candidate is: QByteArray QIODevice::readAll()
    ../xml-interpret/xmlinterpret.cpp:119:65: error: no matching function for call to ‘QIODevice::readAll(QFile&)’
    ../../../QtSDK11/Desktop/Qt/473/gcc/include/QtCore/qiodevice.h:119:16: note: candidate is: QByteArray QIODevice::readAll()@
    I'm sorry if all this looks hopeless...

    But I did read something about QXmlStreamReader just now. As I see it, I have to transfer the tokenString from the parsed text and possibly append it to a string? I tried this:
    @void LaunchPad::parseIndexFiles(QXmlStreamReader& xml) {
    if (xml.name() == "gamename")
    xml.tokenString().append(gameName);
    }
    @
    And it didn't output any specific error, but the error about "no matching function for call to" persisted.


  • Moderators

    In your header, you have the section of code:
    @
    private slots:
    void parseXmlFile();
    void parseIndexFiles();
    void parseTitle();
    void buildIndex();
    void parseObject();
    void parseActors();
    void parseTerrain();
    void parseMusic();
    void parseSounds();
    void parseItems();
    @

    Couple of things...

    These don't need to be slots. Just make them "private:"

    You're not declaring the parameter types that they take.

    If you have
    @void LaunchPad::parseIndexFiles(QXmlStreamReader& xml) @
    in your cpp file, then you need to have the declaration match in your header:
    @ void parseIndexFiles(QXmlStreamReader& xml); @


  • Moderators

    Also, make sure that in your header you reference the QXmlStreamReader type.

    Either add the line
    @ class QXmlStreamReader; @ before the beginning of your Launchpad declaration, or (much less preferably) add @ #include <QtXml/QXmlStreamReader> @ in the header.

    Edit: Nevermind. I see you already have done this. (At any rate, the first option would still be preferable.)



  • or even better
    @
    QT_BEGIN_NAMESPACE
    class QXmlStreamReader;
    QT_END_NAMESPACE
    @



  • Ok, I kept "#include <QXmlStreamReader>" because using "class QXmlStreamReader;" broke everything. And... Why would you prefer that the class definition was used?
    As I see it, from my unexperienced view, you define a class called QXmlStreamReader. Not necessarily what I want to use, which is the streaming functionality... Or does it serve the same purpose?
    And in what way is using #include inferior to defining the class?

    Anyway... When I made the same declarations in the header, I got these errors:
    @../xml-interpret/xmlinterpret.cpp:38:54: error: invalid use of void expression@
    They appear for all the times I refer to other functions, instead of "no function for call to".
    This is a piece of code where the error occurs:
    @gameName.append(this->parseTitle(xml));@
    It's in the if-stack. What have I done wrong this time?



  • @this->parseTitle(xml);@
    does not return anything and you can therefore not append it to gameName.
    I suggest you read up on the basics of C++ before continuing.

    [quote]
    Ok, I kept “#include <QXmlStreamReader>” because using “class QXmlStreamReader;” broke everything. And… Why would you prefer that the class definition was used?
    As I see it, from my unexperienced view, you define a class called QXmlStreamReader. Not necessarily what I want to use, which is the streaming functionality… Or does it serve the same purpose?
    And in what way is using #include inferior to defining the class?
    [/quote]
    It's called forward declaration and speeds up compilation. The #include <QXmlStreamReader> should go in the .cpp file.
    "On when to use forward declaration":http://www-subatech.in2p3.fr/~photons/subatech/soft/carnac/CPP-INC-1.shtml



  • [quote author="loladiro" date="1313352748"]
    I suggest you read up on the basics of C++ before continuing.
    [/quote]
    Now that was a very helpful suggestion. (I mean it.) My C++ is as good as rusted away/I don't know much C++.
    I'm sorry for bothering all of you with this, but I'll be back in this thread when I understand more of how C++ works.



  • [quote author="HowyHappy" date="1313354049"]
    [quote author="loladiro" date="1313352748"]
    I suggest you read up on the basics of C++ before continuing.
    [/quote]
    Now that was a very helpful suggestion. (I mean it.) My C++ is as good as rusted away/I don't know much C++.
    I'm sorry for bothering all of you with this, but I'll be back in this thread when I understand more of how C++ works.[/quote]

    No problem. We will be more than glad to help when you're back :) .



  • Wow, quickly reading through the basics helped a lot. Now, I have the initial Xml parsed to strings, and I'm ready to proceed with building DOM-trees.
    ...When I learn how to build them.
    I must say: QXmlStreamReader is simple to use once you understand how it works.


  • Moderators

    [quote author="HowyHappy" date="1313435556"]Now, I have the initial Xml parsed to strings, and I'm ready to proceed with building DOM-trees.[/quote]

    If you're wanting a DOM tree, perhaps your best bet would be simply using "QDomDocument":http://doc.qt.nokia.com/latest/qdomdocument.html rather than building one from scratch with strings read from an QXmlStreamReader.

    But, it all depends on your use case.



  • Well, of course, I won't use QXmlStreamReader to build a DOM tree. It wouldn't be sufficient for the amounts of data this program should be able to handle. In this usecase, I want there to be indexes for different kinds of objects that can be pulled easily into a renderer and other components. So, f.ex., you'd be able to pull trees of all sorts, and it will load the different properties of it like texture and model without further fuzz.
    For this purpose, a QDomDocument will do perfectly.
    I have all the features and ways of doing it planned well. The thing missing is writing it.


  • Moderators

    Ok... I was just making sure you knew the options. Since you'd self-proclaimed yourself as a "newbie" then there was a little room for interpretation (and guessing) about what you knew about and what you didn't. :-)

    Good luck with your coding!



  • I think I've made some progress, but so far I've taken it after my own perception of what does what.
    This is the code:
    @void LaunchPad::parseActors() {
    //Open the file and assign it to the document
    QFile actorFile(globalActorsFile);
    actorFile.open(QIODevice::ReadOnly | QIODevice::Text);
    actorIndex.setContent(&actorFile);

    //Start building the DOM tree
    QDomElement actorElement;
    QDomNodeList actors = actorElement.elementsByTagName("sysName");
    

    }@
    Will this build me a usable DOM tree?
    This is an example of the target document:
    @<actors>
    <actor name="hero" sysName="hero">
    <model>act_hero_001.zact</model>
    <texture>act_hero_001.ztex</texture>
    </actor>
    <actor name="Princess" sysName="princess">
    <type>NPC</type>
    <model>act_princess_001.zact</model>
    <texture>act_princess_001.ztex</texture>
    <talk>true</talk>
    <logic>act_princess.zlgc</logic>
    </actor>
    </actors>@
    Would I be able to fetch all the child nodes of the actor called "hero"?
    Or am I only halfway?

    And I'm also trying to draw a test-window, just to see that the text gets parsed. So I put in this function:
    @void LaunchPad::drawTestWindow() {
    QWidget titleWindow;
    titleWindow.setWindowTitle(gameName);
    titleWindow.show();
    }@
    But it doesn't seem to work well so far.
    I get this weird error, it says:
    @/usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/../../../crt1.o: In function _start': (.text+0x20): undefined reference tomain'
    collect2: ld returned 1 exit status@
    I bet that I've forgotten to enter something that is required by QWidget or displaying something in general..?


  • Moderators

    Where is your main()? Looks like the linker can't find it.



  • I'm having a problem with main()...
    I don't know how to use it.
    I've written this so far:
    @#include <QtGui/QApplication>
    #include <QWidget>

    #include "xmlinterpret.h"

    int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    return app.exec();
    

    }
    @
    But I don't understand how to make it show the widget I configured...
    I assume that I have to use a pointer somewhere?


  • Moderators

    @
    #include <QtGui/QApplication>
    #include "somewidget.h"

    int main(int argc, char *argv[])
    {
    QApplication app(argc, argv);

    SomeWidget sw;  
    sw.show();
    
    return app.exec&#40;&#41;;
    

    }
    @

    or just look at pretty much any of the examples in the docs.



  • I've successfully implemented the int main function now. I chose to put it in the same file, because then I won't have to use pointers, and this is basicly the launchpad for the engine where it all begins.

    Ok, new problem:
    The program is nicely written now, but I'm not entirely sure on how to use QFile.
    So far, I've used it this way:
    @QFile xmlFile;
    xmlFile.setFileName("game.xml");@
    And:
    @QFile actorFile(globalActorsFile);@
    I also set QDir to the current directory in main() like so:
    @QDir::setCurrent(".");@
    But when I try to start the program, I get "QFSFileEngine::open: No file name specified".
    What have I done wrong?



  • Relative paths are resolved relative to the current directory. The dot "." represents the current directory. So our setCurrent is nothing else than "change the current directory to the current directory".

    I guess you want to set the current directory to that folder that holds the executable. If so, then use this:

    @
    QDir::setCurrent(qApp->applicationDirPath());
    @

    Or better prepend your file path with that string.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.