How to make xml parser more clear - c++

I have an xml file built in this way:
<?xml version="1.0" encoding="UTF-8"?>
<Draw>
<Input>
<Cells>
<width>100</width>
<height>28</height>
</Cells>
<Column>custom</Column>
<Custom>
<header id="0">one</header>
<header id="1">two</header>
<header id="2">three</header>
<header id="3">four</header>
<header id="4">five</header>
</Custom>
</Input>
<Output>
<Cells>
<width>82</width>
<height>20</height>
</Cells>
<Column>upper</Column>
<Custom>
<header id="0">alfa</header>
<header id="1">beta</header>
<header id="2">gamma</header>
<header id="3">delta</header>
<header id="4">epsilon</header>
</Custom>
</Output>
</Draw>
And I’m trying to extrapolate the values ​​of the header tag: since we have two sets of header tags (Input and Output) the only working code that I managed to work for now is this:
void MainWindow::readXmlFile() {
QString target;
QFile* file = new QFile("System/Settings.xml");
/* If we can't open it, let's show an error message. */
if (!file->open(QIODevice::ReadOnly | QIODevice::Text)) return;
QXmlStreamReader xmlReader(file);
/* We'll parse the XML until we reach end of it.*/
while(!xmlReader.atEnd() && !xmlReader.hasError()) {
QXmlStreamReader::TokenType token = xmlReader.readNext();
if(token == QXmlStreamReader::StartDocument) {
continue;
}
/* If token is StartElement, we'll see if we can read it.*/
if (token == 4) {
if (xmlReader.name() == "Input" || xmlReader.name() == "Output") {
target = xmlReader.name().toString();
while (!(xmlReader.tokenType() == QXmlStreamReader::EndElement && xmlReader.name() == target)) {
if (xmlReader.tokenType() == QXmlStreamReader::StartElement) {
qDebug() << xmlReader.name().toString();
if (xmlReader.name() == "width") {
QString num = xmlReader.readElementText();
//input->horizontalHeader()->setDefaultSectionSize(num.toInt());
}
if (xmlReader.name() == "height") {
QString num = xmlReader.readElementText();
//input->verticalHeader()->setDefaultSectionSize(num.toInt());
}
if (xmlReader.name() == "header") {
//headers->append(xmlReader.readElementText());
//qDebug() << xmlReader.readElementText();
}
//input->setHorizontalHeaderLabels(header);
}
xmlReader.readNext();
}
}
}
}
/* Error handling. */
if(xmlReader.hasError()) {
QMessageBox::critical(this,
"QXSRExample::parseXML",
xmlReader.errorString(),
QMessageBox::Ok);
}
xmlReader.clear();
}
Since this code seems very repetitive, especially from line 15 to 18, could you help me to make it a little cleaner? The examples in the web are not very explanatory.
Thanks in advance

I usually use lambda expressions. You will need to add CONFIG += c++11 to your .pro file.
Then define utilities for most repetitive patterns: for instance
/* If token is StartElement, we'll see if we can read it.*/
if (token == 4) {
auto name = [&]() { return xmlReader.name().toString(); };
auto type = [&]() { return xmlReader.tokenType(); };
auto num = [&]() { return xmlReader.readElementText().toInt(); };
if (name() == "Input" || name() == "Output") {
target = name();
while (!(type() == QXmlStreamReader::EndElement && name() == target)) {
if (type() == QXmlStreamReader::StartElement) {
qDebug() << name();
if (name() == "width") {
input->horizontalHeader()->setDefaultSectionSize(num());
}
else if (name() == "height") {
input->verticalHeader()->setDefaultSectionSize(num());
}
else if (name() == "header") {
//headers->append(xmlReader.readElementText());
//qDebug() << xmlReader.readElementText();
}
//input->setHorizontalHeaderLabels(header);
}
xmlReader.readNext();
}
}
unrelated warning: your code is leaking memory, instead of allocating with new, consider the simpler stack allocation:
QFile file("System/Settings.xml");
/* If we can't open it, let's show an error message. */
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return;
QXmlStreamReader xmlReader(&file);

I encountered the same problem few weeks ago, I had to read and write some xml file (arround 20/50 lines).
I started with QXmlStreamReader/Writer and finally give up and use QDomDocument.
The main difference (in terms of performance) between these two objects is QDomDocument loads all the xml file in memory. The syntax is also quite easier with QDomDoc !
See the doc for some written examples: http://qt-project.org/doc/qt-4.8/qdomdocument.html

Related

How to add non compilable configuration file into QT Project?

I have a .txt file in QT project, where I store settings parameters for my app. If I read it that way
void MainWindow::ReadApplicationSettings()
{
QFile cfgFile("config.txt");
if(!cfgFile.exists())
{
qDebug() << "Cannot find config file.";
}
else
{
ParseConfigFile(cfgFile);
SetCfg();
}
cfgFile.close();
}
And Parse it:
void MainWindow::ParseConfigFile(QFile &cfgFile)
{
QString line;
if (cfgFile.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream stream(&cfgFile);
while (!stream.atEnd())
{
line = stream.readLine();
std::istringstream iss(line.toStdString());
std::string id, eq, val;
bool error = false;
if (!(iss >> id))
{
error = true;
}
else if (id[0] == '#')
{
continue;
}
else if (!(iss >> eq >> val >> std::ws) || eq != "=" || iss.get() != EOF)
{
error = true;
}
if (error) { throw std::runtime_error("Parse error"); }
cfgMap[id] = std::stoi(val);
}
}
}
The file exists and when the parsing begin the content of the file is empty
The result of: line = stream.readLine(); is "".
If I add the file like a resource file and opened this way:
QFile cfgFile(":config.txt");
It's working correct, but the problem is that the config file is compiled and when you have to change some value, the project must be rebuild to take effect
I tried to compose the path like that QDir::currentPath + "/config.txt", but not working as well.
Another option is the undocumented feature "file_copies" in Qt 5.6 that you can use like this:
CONFIG += file_copies
configfiles.path = $$OUT_PWD
configfiles.files = $$PWD/config.txt
COPIES += configfiles
Found here: https://stackoverflow.com/a/54162789/6842395
If you don't like this method, that post has some more options to choose.
By the way, loooking at your ParseConfigFile() method seems that your config.txt is a collection of lines with the format: key = value, very similar to a classic INI file. Maybe you could use QSettings third constructor like this:
QSettings settings("config.txt", QSettings::IniFormat);
You can use QMAKE_EXTRA_TARGET in your profile like:
copyfile.commands += $${QMAKE_COPY} $$system_path($$PWD/config.txt) $$system_path($$DESTDIR/)
first.depends = $(first) copyfile
QMAKE_EXTRA_TARGETS += first copyfile
But ensure your $$DESTDIR is correct.

QXMLStreamReader is skipping over text elements

I am writing out an XML file with some data. Later I want to read the XML file and use the data in my program. However, when I read it back in, QXMLStreamReader seems to be skipping over some child elements.
Here is a snip from my XML file:
<?xml version="1.0" encoding="UTF-8"?>
<TEMPROOT>
<Parent1>
<C0>238.195|1401.12</C0>
<C1>795.475|1087.65</C1>
<C2>995.748|756.766</C2>
</Parent1>
<Parent2>
<left>248</left>
<right>671</right>
<Width>496</Width>
<Height>583</Height>
</Parent2>
<Parent3>
<Number>9</Number>
<Blue>4</Blue>
<Red>5</Red>
</Parent3>
</TEMPROOT>
It reads Parent1 and its children correctly. My code recognizes Parent2, but skips over its children and goes directly for Parent3 where it reads its children correctly.
Here is my code where I WRITE out the XML:
QXmlStreamWriter xmlWriter(&file);
xmlWriter.setAutoFormatting(true);
xmlWriter.writeStartDocument();
xmlWriter.writeStartElement("TEMPROOT");
xmlWriter.writeStartElement("Parent1");
xmlWriter.writeTextElement("C0", str0);
xmlWriter.writeTextElement("C1", str1);
xmlWriter.writeTextElement("C2", str2);
xmlWriter.writeEndElement();
xmlWriter.writeStartElement("Parent2");
xmlWriter.writeTextElement("left", QString::number(rectCoord[0]));
xmlWriter.writeTextElement("right", QString::number(rectCoord[1]));
xmlWriter.writeTextElement("Width", QString::number(rectCoord[2]));
xmlWriter.writeTextElement("Height", QString::number(rectCoord[3]));
xmlWriter.writeEndElement();
xmlWriter.writeStartElement("Parent3");
xmlWriter.writeTextElement("Number", strNum);
xmlWriter.writeTextElement("Blue", strBlue);
xmlWriter.writeTextElement("Red", strRed);
xmlWriter.writeEndElement();
xmlWriter.writeEndElement();
xmlWriter.writeEndDocument();
Here is how I READ in the XML file:
QXmlStreamReader xmlReader(&file);
QVector<QString> Qv1;
QVector<double> QV2;
QVector<QString> Qv3;
while (!xmlReader.isEndDocument())
{
xmlReader.readNext();
QString Name = xmlReader.name().toString();
if (Name == "Parent1")
{
while (xmlReader.name().toString() != "Parent2")
{
if (xmlReader.name().toString().at(0) == "C")
{
QV1.append(xmlReader.readElementText());
}
xmlReader.readNext();
}
}
else if (Name == "Parent2")
{
while (xmlReader.name().toString() != "Parent3")
{
if (xmlReader.name().toString() == "left")
{
QV2.append(xmlReader.readElementText().toDouble());
}
else if (xmlReader.name().toString() == "right")
{
QV2.append(xmlReader.readElementText().toDouble());
}
else if (xmlReader.name().toString() == "Width")
{
QV2.append(xmlReader.readElementText().toDouble());
}
else if (xmlReader.name().toString() == "Height")
{
QV2.append(xmlReader.readElementText().toDouble());
}
xmlReader.readNext();
}
}
else if (Name == "Number")
{
Qv3.append(xmlReader.readElementText());
}
else if (Name == "Blue")
{
Qv3.append(xmlReader.readElementText());
}
else if (Name == "Red")
{
Qv3.append(xmlReader.readElementText());
}
}
The MAIN issue is that the Name variable, which is the name of the current XML element on xmlReader goes from Parent2 straight to Parent3 ignoring the children of Parent2. The children of Parent1 and Parent3 are being read in with no problems. Am I doing something wrong when I read or write the xml?

parsing xml file with qtxml c++

i'm just trying to parse an xml file using QtXml lib , so i need some help b'cause i don't understand how it's work :$
so this is my xml file config.xml :
<configuration>
<user>
<foo>Something</foo>
<bar>Something too</bar>
</user>
<dll_version>
<v1>appv1</v1>
<v2>appv2</v2>
<v3>appv3</v3>
</dll_version>
</configuration>
note : dll version it's a function that get version of an app from reg and compare it to value in xml file .
what i want to do :
Create function like : GetConfValue(QString conftype, QString confname)
that return the Config Value example : GetConfValue("user", "foo") return "Something"
thanks :)
Once in past, I also needed similar functionality. I used code as below. This may not be best solution, but i worked for me.
QString GetConfValue(QString conftype, QString confname) {
QString ret = "";
QFile file("config.xml");
if (file.open(QFile::ReadOnly | QFile::Text)) {
QXmlStreamReader reader;
reader.setDevice(&file); //(file);
reader.readNext();
while (!reader.atEnd()) {
if (reader.name() == "configuration" && reader.isStartElement()) {
reader.readNext();
while (!reader.atEnd()) {
if (reader.name() == conftype && reader.isStartElement()) {
reader.readNext();
while (!reader.atEnd()) {
if (reader.name() == confname && reader.isStartElement())
return reader.readElementText();
reader.readNext();
}
}
reader.readNext();
}
}
reader.readNext();
}
} else
qDebug() << qPrintable(file.errorString());
return ret;
}

Parse XML with QXmlStreamReader

I created this xml file with QXmlStreamWriter:
<?xml version="1.0" encoding="UTF-8"?>
<Draw>
<Input>
<Column title="A"/>
<Column title="B"/>
<Column title="C"/>
<Column title="D">
<item id="0">Bayer Leverkusen</item>
<item id="1">Benfica</item>
<item id="2">Villareal</item>
<item id="3">Montpellier</item>
</Column>
</Input>
</Draw>
I would like to create a Vector of String containing all the items inside the tag Column title="D": Now, I know how to create a QVector and how they fit elements on the inside, I just have to figure out how I can do this by extrapolating information from an xml file.
Can you help me?
You can use the QXmlStreamReader to iterate through the XML elements and find the <Column title="D"> element. Once you found it, the readNextStartElement() in combination of skipCurrentElement() can be used to read its all child elements.
Let's assume that the XML document you shown in your examle can be read from the xmlDocument object. To extract all <item> elements from <Column title="D"> element with appropriate error checking, you can do the following:
QXmlStreamReader xmlIterator(xmlDocument);
QVector<QString> output;
for(; !xmlIterator.atEnd(); xmlIterator.readNext()) {
if(isStartElementOfColumnD(xmlIterator)) {
while(xmlIterator.readNextStartElement()) {
if(isItemElement(xmlIterator))
output.append(xmlIterator.readElementText());
else
xmlIterator.skipCurrentElement();
}
}
}
if(xmlIterator.hasError())
qCritical() << "Error has occurred:" << xmlIterator.errorString();
else
qDebug() << output;
In the example above I used two predicates to hide the long and hardly readable validation of xmlIterator. These are the following:
inline bool isStartElementOfColumnD(const QXmlStreamReader& xmlIterator) {
return xmlIterator.isStartElement() && xmlIterator.name() == "Column" &&
xmlIterator.attributes().value("title") == "D";
}
inline bool isItemElement(const QXmlStreamReader& xmlIterator) {
return xmlIterator.name() == "item" &&
xmlIterator.attributes().hasAttribute("id");
}
Sample result:
QVector("Bayer Leverkusen", "Benfica", "Villareal", "Montpellier")
I would write it in the following way:
QVector<QString> store;
[..]
if (reader.readNextStartElement() && reader.name() == "Draw") {
while (reader.readNextStartElement() && reader.name() == "Input") {
while (reader.readNextStartElement()) {
QXmlStreamAttributes attr = reader.attributes();
if (reader.name() == "Column" && attr.value("title").toString() == "D") {
while(!(reader.isEndElement() && reader.name() == "Column")) {
if (reader.isStartElement() && reader.name() == "item") {
QString text = reader.readElementText();
store.append(text);
}
reader.readNext();
if (reader.hasError()) {
// Handle error.
QString msg = reader.errorString();
break;
}
}
} else {
reader.readNext();
}
}
}
} else {
reader.raiseError("Expected <Draw> element");
}
[..]

Xerces-C XML schema validation not working

Trying to get Xerces-C to validate an XML file against a schema file but with no luck. The constructor below takes in the location of the XML file and the schema file and sets relevent member variables:
Config::Config(const std::string& schemaFile, const std::string& XMLFile)
: m_schemaFile(schemaFile),
m_XMLFile(XMLFile)
{
{
//initialize
try
{
xercesc::XMLPlatformUtils::Initialize();
xalanc::XPathEvaluator::initialize();
}
catch (xercesc::XMLException& e)
{
throw XercesInitialisationException();
}
}
{
//validate XML
xercesc::XercesDOMParser m_domParser;
if (NULL == m_domParser.loadGrammar(m_schemaFile.c_str(), xercesc::Grammar::SchemaGrammarType))
{
//schema file could not be loaded
throw SchemaLoadException();
}
ParserErrorHandler errorHandler;
m_domParser.setErrorHandler(&errorHandler);
m_domParser.setDoNamespaces(true);
m_domParser.setDoSchema(true);
m_domParser.setValidationConstraintFatal(true);
m_domParser.setValidationSchemaFullChecking(true);
m_domParser.parse(m_XMLFile.c_str());
if (NULL == m_domParser.getDocument() || NULL == m_domParser.getDocument()->getDocumentElement())
{
throw XMLLoadException();
}
if (0 == m_domParser.getErrorCount())
{
std::cout << "Number of schema validation errors: " << m_domParser.getErrorCount() << std::endl;
}
else
{
//m_validated unsuccessfully against the schema
throw SchemaValidationException();
}
}
{
//set up XPath interpreter
const xalanc::XalanDOMString m_xalanXMLFile(m_XMLFile.c_str());
const xercesc::LocalFileInputSource m_xalanInputSource(m_xalanXMLFile.c_str());
// Initialise XalanSourceTree subsystem...
xalanc::XalanSourceTreeInit sourceTreeInit;
m_liaison = std::auto_ptr<xalanc::XalanSourceTreeParserLiaison>(new xalanc::XalanSourceTreeParserLiaison(m_domSupport));
m_domSupport.setParserLiaison(m_liaison.get());
m_document = m_liaison->parseXMLStream(m_xalanInputSource);
m_prefixResolver = std::auto_ptr<xalanc::XalanDocumentPrefixResolver>(new xalanc::XalanDocumentPrefixResolver(m_document));
m_evaluator = std::auto_ptr<xalanc::XPathEvaluator>(new xalanc::XPathEvaluator);
}
}
The area of the constructor where the XML parse is set up is shown below. This is where I think the problem lies:
//validate XML
xercesc::XercesDOMParser m_domParser;
if (NULL == m_domParser.loadGrammar(m_schemaFile.c_str(), xercesc::Grammar::SchemaGrammarType))
{
//schema file could not be loaded
throw SchemaLoadException();
}
ParserErrorHandler errorHandler;
m_domParser.setErrorHandler(&errorHandler);
m_domParser.setDoNamespaces(true);
m_domParser.setDoSchema(true);
m_domParser.setValidationConstraintFatal(true);
m_domParser.setValidationSchemaFullChecking(true);
m_domParser.parse(m_XMLFile.c_str());
if (NULL == m_domParser.getDocument() || NULL == m_domParser.getDocument()->getDocumentElement())
{
throw XMLLoadException();
}
if (0 == m_domParser.getErrorCount())
{
std::cout << "Number of schema validation errors: " << m_domParser.getErrorCount() << std::endl;
}
else
{
//m_validated unsuccessfully against the schema
throw SchemaValidationException();
}
When the code is compiled and ran everything works but no validation is carried out on the XML against the schema. Which means the XML is parsed even if it does not conform to the schema. After checking I have also been able to ascertain that the errorCount value remains 0 for all schemas.
You must to reference the schema on xml root node like this:
<rootNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="Schema.xsd">