How-to: parsing XML with Qt
Why use XML?
The purpose of this document is to show a couple ways oftransforming data from a saved state
into useful objects in memory,on the Boardcon 2440. Thedocument type best suited to this task is XML,
and as the Boardcon 2440 mark III sports a Qt-everywhereimplementation,which includes QTs XML
parsing objects,parsing XML with QT will be the subjectof this paper.
The advantage of XML over a regular csv file,or customcharacter or binary file is that “records”
or “elements” can be different from one another whilestill being parsed by the same code.
In designing a trivia game,if you decided you needed a Questionobject,which contained
question text,several different answer texts and anindication of which answer is correct,and you
decided to do this with a csv file,your data might looklike this:
1,What color is the sky?,red,green,blue,purple,2,
2,What is the air speed velocity of anAfrican swallow? 24mph,10 mph,5Mm/h,0
3,What’s MontyPython? An English comedy group,a particularly scary snake,A
dishwasher brand,0
… using a file stream reader and tokenizing on stringswith a comma “,” as the delimiter.
But this is problematic,as adding any of the followingwould require lots of additional
development:
· Support for questions with several answers
· Support for questions with external references orresources
· Support for comments
Using the XML alleviates most of these problems.
<Question ID=”1” CorrectAnsID=”3” >
<QuestionText>What color is thesky?</QuestionText>
<Answer ID=”1”>red</Answer>
<Answer ID=”2”>green</Answer>
<Answer ID=”3”>Blue</Answer>
<Answer ID=”4”>Purple</Answer>
</Question>
<Question ID=”2” CorrectAnsID=”1” >
<QuestionText> What is the air speedvelocity of an African
swallow?</QuestionText>
<Answer ID=”1”>24mph</Answer>
<Answer ID=”2”>5mph</Answer>
<Answer ID=”3”>5Mm/h</Answer>
<!-- that’sMega-meters per second,and this is a comment,which
the parser will automatically handle forus.-->
<Answer ID=”4”>Purple</Answer>
</Question>
<!-- below is an example of somefunctionality that could be added,
even though this snippit is still valid XMLand could conceivably be
used by the same code -->
<Question ID=”2” CorrectAnsID=”1” Type=”MultiMedia”>
<Resource Type=”AUdio”>monty-airspeed.mp3</Resource>
<Resource Type=”IMAGE”>monty-swallow.jpg</Resource>
<QuestionText> What’s Monty Python ? </QuestionText>
<Answer ID=”1”>24mph</Answer>
<Answer ID=”2”>5mph</Answer>
<Answer ID=”3”>5Mm/h</Answer>
<!-- that’sMega-meters per second,and this is a comment -->
<Answer ID=”4”>Purple</Answer>
</Question>
Qt is a sophisticated high-level framework on top of C++,as such it provides several objects to
access elements embedded in an XML file. It includes acomplete implementation as per the W3C’s
Document Object Model (“DOM”) called QDomDocument,but italso includes an easier to use
QXmlStreamReader,which abstracts away from the siblingtree-like nature of XML and just gives you a
serialized view.
Writing your XML document
General resources:
· On linux,gedit provides basic XML highlighting if theextension is xml.
· On windows,notepad++ can be used to do the same.
· The XML plugin to eclipse does not provide highlighting,it abstracts further,into a 2-column
table with drop down menus.
· XML validators. Use this to ensure your XML document isvalid:
o http://www.w3schools.com/xml/xml_validator.asp
Writing the Qt XML parsingcode
Open a terminal,and change to your projects directory:
>cd~/cmpt433/YourUserName/private/myApps/YourProject
Adding support for XML to yourproject
First,modify your projects .pro to indicate to the QTpre-compiler that you’ll need XML parsing objects:
>gedit ./yourProject.pro
#-------------------------------------------------
#
# Project created by QtCreator2011-11-12T15:31:45
#
#-------------------------------------------------
QT += core gui xml
TARGET = YourProject
...
Including the needed Libraries
Include the libraries “QXmlStreamReader “,“QFile”,and “QDir”in the header file of the object that’s
going to be reading the XML. You may not need the lattertwo,but they are very helpful.
In your code editor of choice,or with gedit:
>gedit ./yourXMLParsingObject.h
#include <QObject>
#include <QDebug>
#include<QXmlStreamReader>
#include <QFile>
#include <QDir>
class YourXMLParsingObject ...
Get the XML file to parse
Navigating directories can be tricky,though it is notthe subject of this how-to,the QDir and QFile
objects can aide in this. If your application can bethought of as a shell then a QDir object can be thought
of as the present-working-directory. Some helpful methodsto note:
· QDir.AbsolutePath() gets the fully qualified path to thedirectory its currently examining.
· QDir.cdUp() moves the QDir object up one level in thedirectory tree
· QDir.cd(Qstring dirName) moves the QDir down one level inthe directory tree to the specified
sub-directory
To allow your application to run on both your target,andany windows or linux development
environments,It’s also helpful to use a constant staticfield or a #define to hold the path of the file,and
a precompiler switch to determine where the file is inyour directory structure. Modify
yourXMLParsingObject.h again toinclude this statement:
class Model ...
private:
const QString xmlFilePath;
...
Followed by modifying the yourXMLParsingObject.cppfile to set that variable,machine
dependent. Unfortunately,because of the nature of C++’shandling of constant object field references
this is going to get ugly,but it does give the desiredeffect:
#include "model.h"
//constructor
yourXMLParsingObject::yourXMLParsingObject(
QObject *parent) :
QObject(parent),AnyDependentObjects(),
#ifdef __arm__ //on the target
xmlFilePath("/mnt/remote/YourProject/myXML.xml")
#else
#ifdef __WIN32 //for those developing onwindows
xmlFilePath("C:\\Users\\You\\QT\\YourProject\\myXML.xml")
#else //for those developing on linux
xmlFilePath("/home/You/QT/YourProject/myXML.xml")
#endif
#endif
{
score = 0;
lives = 4;
...
Some notes
· Because Qt on Windows uses mingw and some ported linuxlibraries,finding a defined
constant to use as a precompiler #ifdef constantis difficult. As of writing,I have not
found one.
· If you wish to add further dependencies after this one,put a comma after each of the
xmlfilePath constructor calls (read: turn “xmlFilePath(…)” into
“xmlFilePath(…),” for each of them) then any additionaldependencies you need
after the last #endif.
· Using the non-escape character for a file path on windows(IE using “/” instead of “\\”) will
work,but windows traditionally handles file paths with a“\” rather than a “/”,so
convention is why “\” is used here.
Parsing the XML file
Given the XML file:
<?xml version="1.0"encoding="UTF-8"?>
<Quiz>
<GlobalVar ID="ODE2C0DE">coolValue with Digits 2.45</GlobalVar>
<QuestionSet>
<Question ID="1">
<Text>What is your favoriteColor?</Text>
<Answer>Blue</Answer>
</Question>
<Question ID="2">
<Text>How much Do I loveQT</Text>
<Answer>At 10:34PM on Friday,not awhole lot</Answer>
</Question>
</QuestionSet>
</Quiz>
Below is known working code with comments explaining thebehavior:
//Assuming somewhere we have a questionobject,similar to:
Struct GlobalVarADT{ int ID; QString text; GlobalVarADT globalVar; Struct Question{ int questionID; QString questionText; QString answerText; } QList<Question> questions; //This is going to be a member function ofyourXMLParsingObject //that takes a filePath and reads each “question” element into a //QList of question objects. voidYourXMLParsingObject::yourXMLParsingMethod(QString filePath){ //create a new file object with the XML filepath. QFile* file = new QFile(xmlFilePath); //test to see if the file is readable andthat it contains text. if(!file->open(QIODevice::ReadOnly |QIODevice::Text)){ //if you wish to perform some action if thefile is inaccessible,//do so here. return; } //Create the XML stream reader. QXmlStreamReader xml(file); //we’re going toloop over the entire xml document //using QXmlStreamReader’s atEnd() method,in addition to //its hasError() method while(!xml.atEnd() &&!xml.hasError()){ //read the next piece of data into thereader. //this will move the reader “over” or “onto” the next //valid element,including any attributes itholds as //well as its value. The returned object is astatus object //which can be tested for EOF,the begging ofthe file,etc. QXmlStreamReader::TokenType token =xml.readNext(); //test to see if the token indicates that we’re at the beginning //of the document. Any calls to the reader’s current data will //give you the xml version numberinformation. if(token == QXmlStreamReader::StartDocument){ //we don’t want anyof this data,it isn’t any element //we need. continue; } //what we’re lookingfor is that start of a valid element if(token == QXmlStreamReader::StartElement) { //any global elements or elements of quizthat you wish //to read in come next if(xml.name() == "GlobalVar"){ //here we’ve gotglobalVar,so I’m going to //save the ID attribute,and save its text tothe //globalVar globalVar.ID = xml.attributes().value(“ID”).toString().toInt(); //So your XML reader was looking at theGlobalVar //element,we need to push it forward (suchthat its //looking at the value field) so that itreads the //text of this current element. xml.readNext(); //now save the xml.text(),this will be thevalue in //the globalvar element (<GlobalVar>HERE</GlobalVar>) globalVar.text = xml.text().toString(); } //Questionset itself doesn’t do anything for us. We need its //children,so just skip this element if(xml.name() == QuestionSet){ continue; } //This is a question,in the name of goodsoftware //engineering I’m deligatingthis off to another //function called “parseQuestion”. if(xml.name() == "Question") { this->parseQuestion(xml); } }//startElement }//while }//function voidYourXMLParsingObject::parseQuestion(QXmlStreamReader& xml){ //check to ensure that we were called in theappropriate spot! if(xml.tokenType() !=QXmlStreamReader::StartElement && xml.name() !="Question"){ qDebug() << "Called XMLparseQuestionElement " << "without a question Element inthe XML stream!"; return; } //create some memory to read the fields into Question* newQuestion = new Question(); //read the attribute fields in first: Youknow that currently the //stream is pointing at the Question element(as was checked by //the prevIoUs if condition),so you can readin the attribute //fields immediately. newQuestion->ID =xml.attributes().value("ID").toString().toInt(); //but now we need to get data from Question'schildren... xml.readNext(); //and now the code looks very similar to thatof the caller! //the way to read the while condition is asfollows: //while it isn’t the casethat we're at the end of an element and //(to protect against nested questions) we'renot looking at a //new question element while(!(xml.tokenType() ==QXmlStreamReader::EndElement && xml.name() =="Question")){ //at the start of an element,otherwiseignore and //keep reading. if(xml.tokenType() ==QXmlStreamReader::StartElement){ //If the element is a text element,save it if(xml.name() == "Text"){ xml.readNext(); newQuestion->questionText = xml.text().toString(); } //otherwise if its an answer element,save it if(xml.name() == "Answer"){ xml.readNext(); newQuestion->answerText = xml.text().toString(); } //otherwise we don’t know what it is,so do nothing,//and read the next thing! } //we’re currentlylooking at some element feld,//so read the next element header! xml.readNext(); }//while //add the new question we just created to thelist. questions.add(newQuestion); //done! return; }from:http://www.cs.sfu.ca/CourseCentral/433/bfraser/other/2011-student-howtos/XML-in-Qt.pdf