C++ XML parser QT - not working - c++

Below is my code & xml file. I am trying to read a xml file and store the contents. But its not working. Please help
int ParseConfigFile::populateUserValues ( string &OS, string &dir, string &logPath )
{
QDomDocument doc;
QDomElement element;
QFile file("config_v1.xml");
if ( doc.setContent( file.readAll() ) == false )
return 1;
element = doc.documentElement();
QDomNodeList list = element.childNodes();
QDomElement firstChild = list.at(0).toElement(); // will return the first child
QDomElement secondChild = list.at(1).toElement();
QDomElement thirdChild = list.at(2).toElement();
QString s1,s2,s3;
s1 = firstChild.text();
s2 = secondChild.text();
s3 = thirdChild.text();
OS = s1.toStdString();
dir = s2.toStdString();
logPath = s3.toStdString();
return 0;
}
and my XML
<?xml version="1.0"?>
<config>
<type>LINUX</type>
<dir>/home/</dir>
<path>/var/log/</path>
</config>

You should instantiate the QDomDocument like this and then your code will work:
if ( doc.setContent(&file) == false )
return 1;
QDomDocument can work with an IO device (like QFile) making this the optimum way to set the QDomDocument's content from a file.
Anyway, if you're trying to write a configuration file reader/writer I suggest you stick with QSettings
QSettings settings("MyCompany", "MyApp");
QString s1,s2,s3;
s1 = settings.value("type").toString();
s2 = secondChild..value("dir").toString();
s3 = thirdChild..value("path").toString();
The configuration file format won't be XML (it will be key=value) but you will hardly ever need to worry about the file itself. To set any value on the file is also easy:
QSettings settings("MyCompany", "MyApp");
settings.setValue("path", "/var/log/");
Your configuration file will look like this:
[General]
path=/var/log/
See: QSettings documentation

Related

Reading a XML file in C++ with TinyXML2

I'm pretty new to using XML in C++ and i'm trying to parse a list of files to download.
THe XML file I'm using is generated via PHP and looks like this :
<?xml version="1.0"?>
<FileList>
<File Name="xxx" Path="xxx" MD5="xxx" SHA1="xxx"/>
</FileList>
The code I'm using in C++ is the following, which I came up using some online tutorials (it's included in some global function):
tinyxml2::XMLDocument doc;
doc.LoadFile("file_listing.xml");
tinyxml2::XMLNode* pRoot = doc.FirstChild();
tinyxml2::XMLElement* pElement = pRoot->FirstChildElement("FileList");
if (pRoot == nullptr)
{
QString text = QString::fromLocal8Bit("Error text in french");
//other stuff
}
else
{
tinyxml2::XMLElement* pListElement = pElement->FirstChildElement("File");
while (pListElement != nullptr)
{
QString pathAttr = QString::fromStdString(pListElement->Attribute("Path"));
QString md5Attr = QString:: fromStdString(pListElement->Attribute("MD5"));
QString sha1Attr = QString::fromStdString(pListElement->Attribute("SHA1"));
QString currentPath = pathAttr.remove("path");
QString currentMd5 = this->fileChecksum(currentPath, QCryptographicHash::Md5);
QString currentSha1 = this->fileChecksum(currentPath, QCryptographicHash::Sha1);
QFile currentFile(currentPath);
if (md5Attr != currentMd5 || sha1Attr != currentSha1 || !currentFile.exists())
{
QString url = "url" + currentPath;
this->downloadFile(url);
}
pListElement = pListElement->NextSiblingElement("File");
}
Problem is, I get an error like "Access violation, this was nullptr" on the following line :
tinyxml2::XMLElement* pListElement = pElement->FirstChildElement("File");
Since I'm far from a pro when it comes to coding and I already searched the internet up and down, I hope that someone here can provide me some pointers.
Have a good day, folks.
I don't know if you have C++17 available, but you can remove a lot of noise by using auto* and if-init-expressions (or rely on the fact that pointers can be implicitly converted to boolean values.)
The main issue with your code is you were not using XMLElement* but instead a XMLNode. The function tinyxml2::XMLDocument::RootElement() automatically gets the top-most element for you.
Because you have an xml declaration at the top, FirstChild returns that...which doesn't have any children, so the rest of the code fails.
By using RootElement tinyxml knows to skip any leading non-element nodes (comments, doctypes, etc.) and give you <FileList> instead.
tinyxml2::XMLDocument doc;
auto err = doc.LoadFile("file_listing.xml");
if(err != tinyxml2::XML_SUCCESS) {
//Could not load file. Handle appropriately.
} else {
if(auto* pRoot = doc.RootElement(); pRoot == nullptr) {
QString text = QString::fromLocal8Bit("Error text in french");
//other stuff
} else {
for(auto* pListElement = pRoot->FirstChildElement("File");
pListElement != nullptr;
pListElement = pListElement->NextSiblingElement("File"))
{
QString pathAttr = QString::fromStdString(pListElement->Attribute("Path"));
QString md5Attr = QString:: fromStdString(pListElement->Attribute("MD5"));
QString sha1Attr = QString::fromStdString(pListElement->Attribute("SHA1"));
QString currentPath = pathAttr.remove("path");
QString currentMd5 = this->fileChecksum(currentPath, QCryptographicHash::Md5);
QString currentSha1 = this->fileChecksum(currentPath, QCryptographicHash::Sha1);
QFile currentFile(currentPath);
if(md5Attr != currentMd5 || sha1Attr != currentSha1 || !currentFile.exists()) {
QString url = "url" + currentPath;
this->downloadFile(url);
}
}
}
}
According to the reference for tinyxml2::XMLNodeFirstChild():
Get the first child node, or null if none exists.
This line will therefore get the root node:
tinyxml2::XMLNode* pRoot = doc.FirstChild();
Meaning when you attempt to find a FileList node within the root node it returns null.
To avoid the access violation, check your pointers are valid before using them. There is an if check for pRoot but the line immediately before it tries to call a function on pRoot. There is no if check for pElement so this is why you get an access violation. As well as checking pointers are valid, consider adding else blocks with logging to say what went wrong (e.g. "could not find element X"). This will help you in the long run - XML parsing is a pain, even with a library like Tinyxml, there are always teething problems like this, so getting into the habit of checki g pointers and logging out helpful messages will definitely pay off.

Write to existing json file

I am using this code to add to my existing JSON file. However It completely overrides my JSON file and just puts one JSON object in it when I would just like to add another item to the list of items in my JSON file. How would I fix this?
Json::Value root;
root[h]["userM"] = m;
root[h]["userT"] = t;
root[h]["userF"] = f;
root[h]["userH"] = h;
root[h]["userD"] = d;
Json::StreamWriterBuilder builder;
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
std::ofstream outputFileStream("messages.json");
writer-> write(root, &outputFileStream);
My recommendation is
Load the file into a Json::Value
Add or change whatever fields you want
Overwrite the original file with the updated Json::Value
Doing this is going to be the least error-prone method, and it'll work quickly unless you have a very large Json file.
How to read in the entire file
This is pretty simple! We make the root, then just use the >> operator to read in the file.
Json::Value readFile(std::istream& file) {
Json::Value root;
Json::Reader reader;
bool parsingSuccessful = reader.parse( file, root );
if(not parsingSuccessful) {
// Handle error case
}
return root;
}
See this documentation here for more information

How to convert xml node data into string in QT [duplicate]

This question already has an answer here:
Reading an XML file using QXmlStreamReader
(1 answer)
Closed 6 years ago.
I have xml file in my local machine. The xml file format is like:
<string>
<Data>
<Name>Sanket</Name>
<Number>0987654321</Number>
<Address>India</Address>
</Data>
<Data>
<Name>Rahul</Name>
<Number>0987654321</Number>
<Address>Maharashtra</Address>
</Data>
</string>
I want to convert this XML file data into String format. Like:
Sanket 0987654321 India
Rahul 0987654321 Maharashtra
What is the easiest way to convert this data in QT using c++.
I am new in that, so please can anyone suggest me some sample code for this?
Thank you in advance.
I tried following code, but that not work for me:
void parseFile()
{
QList<QList<QString> > dataSet;
QString lastError = "";
QFile inFile("test.xml");
if (inFile.open(QIODevice::ReadOnly))
{
QTextStream fread(&inFile);
long totalSize = inFile.size();
QString line;
while(!fread.atEnd())
{
line = fread.readLine();
QList<QString> record = line.split(QString::KeepEmptyParts);
dataSet.append(record);
}
qDebug()<<dataSet;
}else{
lastError = "Could not open "+test.xml+" for reading";
}
}
You could parse the xml elements firstly via QXmlStreamReader and then you can assemble the xml elements into the string how you want.
The problem of you Code is that you only process the text Lines without any xml-syntax processed by the xml class.
You should look at the QtXML classes for which Florent Uguet provided some links.
However I modified the example found here to do what you want (It does that exact thing for your exact input):
#include <QDomDocument>
#include <QFile>
#include <iostream>
#include <QDomNodeList>
int main()
{
QDomDocument doc("mydocument");
QFile file("test.xml");
if (!file.open(QIODevice::ReadOnly))
return 1;
if (!doc.setContent(&file)) {
file.close();
return 1;
}
file.close();
const auto stringTags = doc.elementsByTagName("string");
for(int stringsI = 0; stringsI < stringTags.size(); ++stringsI){
QDomNode stringTag = stringTags.at(stringsI);
for(QDomNode dataTag = stringTag.firstChildElement("Data"); !dataTag.isNull(); dataTag = dataTag.nextSiblingElement("Data")){
for(QDomNode innerTag = dataTag.firstChild(); !innerTag.isNull(); innerTag = innerTag.nextSibling()){
auto val = innerTag.toElement().text();
std::cout << val.toStdString() << " ";
}
std::cout << std::endl;
}
}
return 0;
}
I build it with QtCreator using qmake. For this you should know that you need to put QT += xml in your *.pro file.
Already asked (and with code) : Reading an XML file using QXmlStreamReader
Qt provides a set of classes for handling XML :
http://doc.qt.io/qt-5.7/qtxml-index.html
http://doc.qt.io/qt-5.7/qxmlstreamreader.html
http://doc.qt.io/qt-5.7/qxmlstreamwriter.html
Old C++ classes (not maintained)
http://doc.qt.io/qt-5/qtxml-module.html
Once you have parsed your file using these, you can usually read the individual nodes' inner text or attributes.

Read XML node with RapidXML

I'm using RapidXML to parse XML files and read nodes content but I don't want to read values inside a node, I need to read the content of specific XML nodes "as XML" not as parsed values.
Example :
<node1>
<a_lot_of_xml>
< .... >
</a_lot_of_xml>
</node1>
I need to get the content of node1 as :
<a_lot_of_xml>
< .... >
</a_lot_of_xml>
What I tired :
I tried something but its not really good in my opinion, its about to put in node1, the path of an other xml file to read, I did like this :
<file1ToRead>MyFile.xml</file1ToRead>
And then my c++ code is the following :
ifstream file(FileToRead);
stringstream buffer; buffer << file.rdbuf();
But the problem is users will have a lot of XML files to maintain and I just want to use one xml file.
I think "a lot of XML files" is a better way, so you have a directory of all xml files, you can read the xml file when you need it, good for performance.
Back to the problem, can use the rapidxml::print function to get the xml format.
bool test_analyze_xml(const std::string& xml_path)
{
try
{
rapidxml::file<> f_doc(xml_path.c_str());
rapidxml::xml_document<> xml_doc;
xml_doc.parse<0>(const_cast<char*>(f_doc.data()));
rapidxml::xml_node<>* node_1 = xml_doc.first_node("node1");
if(node_1 == NULL)
{
return false;
}
rapidxml::xml_node<>* plain_txt = node_1->first_node("a_lot_of_xml");
if (plain_txt == NULL)
{
return false;
}
std::string xml_data;
rapidxml::print(std::back_inserter(xml_data), *plain_txt, rapidxml::print_no_indenting); //the xml_data is XML format.
}
catch (...)
{
return false;
}
return true;
}
I'm unfamiliar with rapidxml, but I have done this with tinyxml2. The trick is to read out node1 and then create a new XMLDoc (using tinyxml2 terms here) that contains everything inside of node1. From there, you can use their XMLPrinter class to convert your new XMLDoc (containing everything in node1) to a string.
tinyxml2 is a free download.

Parsin XML file using pugixml

Hi
I want to use XML file as a config file, from which I will read parameters for my application. I came across on PugiXML library, however I have problem with getting values of attributes.
My XML file looks like that
<?xml version="1.0"?>
<settings>
<deltaDistance> </deltaDistance>
<deltaConvergence>0.25 </deltaConvergence>
<deltaMerging>1.0 </deltaMerging>
<m> 2</m>
<multiplicativeFactor>0.7 </multiplicativeFactor>
<rhoGood> 0.7 </rhoGood>
<rhoMin>0.3 </rhoMin>
<rhoSelect>0.6 </rhoSelect>
<stuckProbability>0.2 </stuckProbability>
<zoneOfInfluenceMin>2.25 </zoneOfInfluenceMin>
</settings>
To pare XML file I use this code
void ReadConfig(char* file)
{
pugi::xml_document doc;
if (!doc.load_file(file)) return false;
pugi::xml_node tools = doc.child("settings");
//[code_traverse_iter
for (pugi::xml_node_iterator it = tools.begin(); it != tools.end(); ++it)
{
cout<<it->name() << " " << it->attribute(it->name()).as_double();
}
}
and I also was trying to use this
void ReadConfig(char* file)
{
pugi::xml_document doc;
if (!doc.load_file(file)) return false;
pugi::xml_node tools = doc.child("settings");
//[code_traverse_iter
for (pugi::xml_node_iterator it = tools.begin(); it != tools.end(); ++it)
{
cout<<it->name() << " " << it->value();
}
}
Attributes are loaded corectly , however all values are equals 0. Could somebody tell me what I do wrong ?
I think your problem is that you're expecting the value to be stored in the node itself, but it's really in a CHILD text node. A quick scan of the documentation showed that you might need
it->child_value()
instead of
it->value()
Are you trying to get all the attributes for a given node or do you want to get the attributes by name?
For the first case, you should be able to use this code:
unsigned int numAttributes = node.attributes();
for (unsigned int nAttribute = 0; nAttribute < numAtributes; ++nAttribute)
{
pug::xml_attribute attrib = node.attribute(nAttribute);
if (!attrib.empty())
{
// process here
}
}
For the second case:
LPCTSTR GetAttribute(pug::xml_node & node, LPCTSTR szAttribName)
{
if (szAttribName == NULL)
return NULL;
pug::xml_attribute attrib = node.attribute(szAttribName);
if (attrib.empty())
return NULL; // or empty string
return attrib.value();
}
If you want stock plain text data into the nodes like
<name> My Name</name>
You need to make it like
rootNode.append_child("name").append_child(node_pcdata).set_value("My name");
If you want to store datatypes, you need to set an attribute. I think what you want is to be able to read the value directly right?
When you are writing the node,
rootNode.append_child("version").append_attribute("value").set_value(0.11)
When you want to read it,
rootNode.child("version").attribute("version").as_double()
At least that's my way of doing it!