Better way to use QDomDocument data as text - c++

I am a newbie, I am creating a XML file in which I need to give CRC value to cross check on server. For this I need QDomDocument data as text. For this I am first creating XML file with fake CRC value. Then I open it with QFile and read all data. Then I split data and calculate data CRC. Now I have to rewrite whole file again. I know it is the worst idea ever to write same file twice but as I am a newbie, I don't know how to do it in better style. Here is my code:-
QDomElement docElem = doc.documentElement();
QFile xmlfile(filename);
if(!xmlfile.open(QIODevice::ReadWrite | QIODevice::Text))
{
qDebug("Can not open file device.");
}
xmlfile.resize(0);
QXmlStreamWriter xw;
xw.setDevice(&xmlfile); //set file to XML writer
xw.setAutoFormatting(true);
xw.setAutoFormattingIndent(4);
xw.writeStartDocument();
xw.writeStartElement(fileID); //fileID as the start element
if(docElem.hasAttributes())
{
xw.writeAttribute("xmlns:xs",docElem.attribute("xmlns:xs"));
xw.writeAttribute("xmlns",docElem.attribute("xmlns"));
}
xw.writeTextElement("Frame_Start_ID","STX");
xw.writeTextElement("Frame_Length","1234");
xw.writeTextElement("Source_Device_Id","CDIS_PIS ");
xw.writeTextElement("Destination_Device_Id","DDNS-SERVER ");
xw.writeTextElement("Frame_Code","I");
xw.writeStartElement("Frame_Data");
//inside frame data
xw.writeTextElement("File_Number","1");
xw.writeTextElement("File_Name","Event");
for(int j=0;j<logFields.count();j++)
{
xw.writeTextElement(logFields.at(j),logData.at(j));
}
xw.writeEndElement();
xw.writeTextElement("CRC","14405904");
xw.writeTextElement("Frame_End_Id","ETX");
xw.writeEndDocument();
xmlfile.flush();
xmlfile.close();
QFile xmlfyle(filename);
xmlfyle.open(QIODevice::ReadWrite | QIODevice::Text);
QString content = (QString)xmlfyle.readAll();
QStringList list1 = content.split("<CRC>");
qDebug() << "Split value = " << list1.at(0);
QByteArray crc_new = crc_o.crc_generate_modbus((unsigned char*)list1.at(0).data(),list1.at(0).size());
xmlfyle.resize(0);
QXmlStreamWriter xw_new;
xw_new.setDevice(&xmlfyle); //set file to XML writer
xw_new.setAutoFormatting(true);
xw_new.setAutoFormattingIndent(4);
xw_new.writeStartDocument();
xw_new.writeStartElement(fileID); //fileID as the start element
if(docElem.hasAttributes())
{
xw_new.writeAttribute("xmlns:xs",docElem.attribute("xmlns:xs"));
xw_new.writeAttribute("xmlns",docElem.attribute("xmlns"));
}
xw_new.writeTextElement("Frame_Start_ID","STX");
xw_new.writeTextElement("Frame_Length","1234");
xw_new.writeTextElement("Source_Device_Id","CDIS_PIS ");
xw_new.writeTextElement("Destination_Device_Id","DDNS-SERVER ");
xw_new.writeTextElement("Frame_Code","I");
xw_new.writeStartElement("Frame_Data");
xw_new.writeTextElement("File_Number","1");
xw_new.writeTextElement("File_Name","Event");
for(int j=0;j<logFields.count();j++)
{
xw_new.writeTextElement(logFields.at(j),logData.at(j));
}
xw_new.writeEndElement();
char tab[10];
sprintf(tab,"%d",crc_new.data());
xw_new.writeTextElement("CRC",QString::fromUtf8(tab));
xw_new.writeTextElement("Frame_End_Id","ETX");
xw_new.writeEndDocument();
xmlfyle.flush();
xmlfyle.close();
can anyone suggest me what could be a better way to do this.Thanks

One version of QXmlStreamWriter constructor accepts a QByteArray and writes into the array instead of an output file.
QXmlStreamWriter Class
So what you can do is; using QXmlStreamWriter, prepare data for your XML in a QByteArray, do whatever you need to do with the CRC inside this data; and when everything is done, write this QByteArray to the output file.

Related

How to add object to Json file

I am trying to log data from 3 sensors to a json file. All I want to be able to accomplish is to write the Speed, Latitude and Longitude to a Json file, with an object containing each of the above. That is a json file that contains one route object, n sub objects each of which contain speed, latitude, longitude.
These 3 values I get from 3 global QList lists. Below is the json file which is stored locally. (The double values are not actual values, just for testing purposes)
{
"Sensordata": [
{
"Speed": 1,
"GPSLat":-12.5687,
"GPSLong":26.125546
},
{
"Speed": 1,
"GPSLat":-12.5687,
"GPSLong":26.125546
}
]
}
This is what the json must look like and when I add it must be formatted in the same way
void MainWindow::save_to_json() {
QFile file_obj(".../SensorData.json");
if(!file_obj.open(QIODevice::ReadOnly)){
qDebug()<<"Failed to open "<<"SensorData.json";
exit(1);
}
QTextStream file_text(&file_obj);
QString json_string;
json_string = file_text.readAll();
file_obj.close();
QByteArray data_json = json_string.toLocal8Bit();
QJsonDocument doc = QJsonDocument::fromJson(data_json);
QJsonObject rootObj = doc.object();
QJsonValue SensorData = rootObj.value("SensorData");
if(!SensorData.isArray())
{
// array expected - handle error
}
QJsonArray SensorDataArray = SensorData.toArray();
QJsonObject newObject;
newObject["Speed"] = speed_array.takeFirst();
newObject["GPSLat"] = gps_lat.takeFirst();
newObject["GPSLong"] = gps_long.takeFirst();
SensorDataArray.push_back(newObject);
}
ASSERT: "!isEmpty()" in file /home/username/Qt/5.12.1/gcc_64/include /QtCore/qlist.h, line 347
11:32:55: The program has unexpectedly finished.
11:32:55: The process was ended forcefully.
This is the error the above code creates.
To modify the data, given your example, you need to check if the contained data in the QJsonDocument is an array or a simple object. In your case, I suppose you want to append data to an array. Try something like this:
// Read the data
const QString filename = "example.json";
QJsonDocument doc = read(filename);
// Check that it's an array and append new data
QJsonValue sensorData = doc.value("SensorData");
if (!sensorData.isArray()) {
// if the doc is empty you may want to create it
}
// Get the array and insert the data
auto array = sensorData.array();
array.append(QJsonObject{
{"Speed", speed_array.takeFirst()},
{"GPSLat", gps_lat.takeFirst()},
{"GPSLong",gps_long.takeFirst(),
});
// Restore your sensor data
doc.setObject(QJsonObject{{"SensorData", array}});
// Write the new data
write(filename, doc);
A helper functions to read/write JSON documents may avoid the mistake of open/closing a file:
QJsonDocument read(const QString& filename) {
QFile file(filename);
file.open(QIODevice::ReadOnly | QIODevice::Text);
const QString val = file.readAll();
file.close();
return QJsonDocument::fromJson(val.toUtf8());
}
void write(const QString& filename, const QJsonDocument& document) {
QFile file(filename);
file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate);
file.write(document.toJson());
file.close();
}
Updates
To not overwrite the original doc, you must update the field of the root object or use QJsonValueRef.
// Get a reference to your array
QJsonObject root = doc.object();
QJsonValueRef ref = root.find("SensorData").value();
// get the array and insert the data
QJsonArray array = ref.toArray();
array.append(QJsonObject{
{"Speed", speed_array.takeFirst()},
{"GPSLat", gps_lat.takeFirst()},
{"GPSLong",gps_long.takeFirst(),
});
// Update the ref with the new data
ref = array
// update the modified data in the json document
doc.setObject(root);

Serializing/parsing multiple objects in one file in Qt C++

I need to serialize and parse multiple objects from my project, in order to save/load them when needed.
My objects will have exactly the same components : a QString name, an integer id, a QString description, and two integer x, y.
I'll need something like this :
{"name":"toto", "id":"42", "description":"tata", "x":"20", "y":"50"}
So I'll build my QJsonObject like this :
QJsonObject json;
json["id"] = object_to_serialize.get_id();
json["name"] = object_to_serialize.get_name();
json["description"] = object_to_serialize.get_description();
json["x"] = object_to_serialize.get_x();
json["y"] = object_to_serialize.get_y();
QJsonDocument filedoc(json);
file.write(filedoc.toJson);`
And in the file it will appear like this :
{"name":"toto", "id":"42", "description":"tata", "x":"20", "y":"50"}
{"name":"toto2", "id":"44", "description":"tata2", "x":"25", "y":"547"}
{"name":"toto3", "id":"46", "description":"tata3", "x":"21", "y":"580"}
My serialiser will take in parameter the object, the savefile name, and transform the object into a QJsonObject. It will need then to read the file to check if an object with the same id is here. If it is here, it will need to replace it, and if it is not, it will append it.
I'm a little lost between my serialization options and how to read it ;
Should I make a QJsonArray with multiple QJsonObject inside or QJsonObject with QJsonArrays ?
When I read it, I will need to check for the id ; but will a
foreach(object.value["id"] == 42)
//create the QJsonObject from the one with 42 and change it with the new data
will do to parse the object and not all of them ? Is there a better way ?
Thank you in advance for your answers.
You can have an array of json object, each of them having an ID so you can parse the relevant ones.
Although you could also parse all of them and add them in a map, as long as you don't have very heavy files it should be fine.
void parseJson(const QString &data)
{
QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8());
if (doc.isNull())
{
war("invalid json document");
return;
}
QJsonArray jsonArray = doc.array();
foreach (const QJsonValue & value, jsonArray) {
QJsonObject obj = value.toObject();
if (obj.contains("id"))
{
if (obj["id"].toInt() == yourId) parseObject(obj);
}
}
}
void parseObject(const QJsonObject &obj)
{
if (obj.contains("valueA")) valueA = obj["valueA"].toDouble();
if (obj.contains("valueB")) valueB = obj["valueB"].toDouble();
}
This will work just fine if your file is not too big
Bigger Files
Now if you have very large file, it might be an issue to load it all in memory and parse it.
Since your structure is always the same and quite simple, JSON might not be the best choice, one more efficient method would be to do your own parser (or use probably some existing ones) that could read the file and process it as a stream.
Another method, would be to have one JSON entry per line preceded by an ID with a fixed number of digit. Load this in a QHash lookup and then only read id of interest from the file and only parse a small section.
// This code is not tested and is just to show the principle.
#define IDSIZE 5
QHash<int64, int64> m_lookup; // has to be global var
// For very large file, this might take some time and can be done on a separate thread.
// it needs to be done only once at startup (given the file is not modified externally)
void createLookup(const QString &fileName)
{
QFile inputFile(fileName);
if (inputFile.open(QIODevice::ReadOnly))
{
QTextStream in(&inputFile);
while (!in.atEnd())
{
int position = in.pos(); // store the position in the file
QString line = in.readLine();
int id = line.mid(0,IDSIZE).toInt(); // 5 digit id (like 00001, 00002, etc...
m_lookup[id] = position + IDSIZE;
}
inputFile.close();
}
}
QString getEntry(const QString &fileName, int64 id)
{
if (m_lookup.contains(id))
{
QFile inputFile(fileName);
if (inputFile.open(QIODevice::ReadOnly))
{
inputFile.seek(m_lookup[id]);
QString data = inputFile.readLine();
inputFile.close();
return data;
} else {
return QString(); // or handle error
}
} else {
return QString(); // or handle error
}
}
// use example
QString data = getEntry(id);
if (data.length() > 0)
{
QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8());
if (!doc.isNull())
{
// assign your variables
}
}
and your data file looking like this:
00042{"name":"toto", "id":"42", "description":"tata", "x":"20", "y":"50"}
00044{"name":"toto2", "id":"44", "description":"tata2", "x":"25", "y":"547"}
00046{"name":"toto3", "id":"46", "description":"tata3", "x":"21", "y":"580"}
The advantage of this method, it will only read the entry of interest, and avoid having to load MB or GB of data in memory just to get a specific entry.
This could further be improved with a lookup table stored at the beginning of the file.

Write QPainterPath to XML

I have a QPainterPath that is being drawn to my QGraphicsScene and I am storing the points of the path as they are drawn into a QList.
My question is how do I now save those points out to an xml ( I assume this would work best ) as they are drawn? My goal is when the app closes, I read that xml, and the path is immediately re-drawn into the scene.
Here is the method I have setup for the writing, which I would call everytime I write a new point to the path.
void writePathToFile(QList pathPoints){
QXmlStreamWriter xml;
QString filename = "../XML/path.xml";
QFile file(filename);
if (!file.open(QFile::WriteOnly | QFile::Text))
qDebug() << "Error saving XML file.";
xml.setDevice(&file);
xml.setAutoFormatting(true);
xml.writeStartDocument();
xml.writeStartElement("path");
// --> no clue what to dump here: xml.writeAttribute("points", ? );
xml.writeEndElement();
xml.writeEndDocument();
}
Or maybe this isn't the best way to go about this?
I think I can handle the reading and re-drawing of the path, but this first part is tricking me up.
You may use binary file:
QPainterPath path;
// do sth
{
QFile file("file.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file); // we will serialize the data into the file
out << path; // serialize a path, fortunately there is apriopriate functionality
}
Deserialization is similar:
QPainterPath path;
{
QFile file("file.dat");
file.open(QIODevice::ReadOnly);
QDataStream in(&file); // we will deserialize the data from the file
in >> path;
}
//do sth

How to use QProgressDialog along with QDomDocument save function

To make a long story short I have a program that uses the QDomDocument class to create an xml file and then uses the save() function to save it to a text stream object. So basically its
QDomDocument somedoc;
//create the xml file, elements, etc.
QFile io(fileName);
QTextStream out(&io);
doc.save(out,4);
io.close();
I want to be able to show the progress of the save using the QProgressDialog class, but I'm having a hard time figuring it out. Is there a way I can incrementally check to see if the file is through processing and just update the progress? Any suggestions? Thanks.
Firstly, I thought that we can find answer in Qt source code, but it was not so simple, so I found easier solution, just use toString() method and write it as usual file. For example:
QStringList all = doc.toString(4).split('\n');//4 is intent
int numFiles = all.size();
QProgressDialog *progress = new QProgressDialog("Copying files...", "Abort Copy", 0, numFiles, this);
progress->setWindowModality(Qt::WindowModal);
QFile file("path");
file.open(QIODevice::WriteOnly);
progress->show();
QTextStream stream(&file);
for (int i = 0; i < numFiles; i++) {
progress->setValue(i);
if (progress->wasCanceled())
break;
stream << all.at(i) << '\n';
QThread::sleep(1);//remove these lines in release, it is just example to show the process
QCoreApplication::processEvents();
}
progress->setValue(numFiles);
file.close();
If you want to look at the source code of QDomDocument::save(), you can find it in
qt-everywhere-opensource-src-5.4.1.zip\qt-everywhere-opensource-src-5.4.1\qtbase\src\xml\dom
or just on the GitHub.

Uploading .csv or .txt file to populate QTableView

Recently I was working on a gui application & I wanted to save the data of QTableView in a .csv or .txt file. I Used the guidance received during this question which made me think if the reverse is also possible; i.e. if the QTableView can be populated from a .csv or .txt file. Once again I would prefer staying with a model based design such as QTableView instead of item based QTableWidget.
Any code-snippet or tutorial-documentation would be really helpful.
Consider a test.csv file (it could be generated by any text editor):
And the textstream behind is (if generated by programming):
1,2,3,\n4,5,6,\n7,8,9,\n10,11,12,\n13,14,15,
If opened in Microsoft Office Excel, it might looked like:
To read this .csv file to the model of your QTableView:
QStandardItemModel *model = new QStandardItemModel;
QFile file("test.csv");
if (file.open(QIODevice::ReadOnly)) {
int lineindex = 0; // file line counter
QTextStream in(&file); // read to text stream
while (!in.atEnd()) {
// read one line from textstream(separated by "\n")
QString fileLine = in.readLine();
// parse the read line into separate pieces(tokens) with "," as the delimiter
QStringList lineToken = fileLine.split(",", QString::SkipEmptyParts);
// load parsed data to model accordingly
for (int j = 0; j < lineToken.size(); j++) {
QString value = lineToken.at(j);
QStandardItem *item = new QStandardItem(value);
model->setItem(lineindex, j, item);
}
lineindex++;
}
file.close();
}
(You could manipulate the code to meet you table format)
[Result]