I'm reading, parsing and writing back a JSON file in a QT project. Part of the requirements is that all entry's should be the same written out as declared in the source file.
One of the entries looks somewhat like this:
"SomeVal": 1.23141241242140
When I read the JSON object, the last zero (0) is removed. That does make sense, since it is not needed for normal use cases. But, since I need to preserve the whole correct number, including that last zero, this is incorrect for me. Any clue on how I can bypass this problem?
The code that reads the JSON file:
QString rawJson = "";
QFile jsonFile(filePath);
if(jsonFile.exists())
{
jsonFile.open(QFile::ReadOnly|QFile::Text);
rawJson = jsonFile.readAll();
jsonFile.close();
}
else
{
return false;
}
QJsonParseError json_parse_error;
QJsonDocument json_doc = QJsonDocument::fromJson(rawJson.toUtf8(), &json_parse_error);
if(json_parse_error.error != QJsonParseError::NoError)
{
emit SignalMessageCritical(QString(json_parse_error.errorString()));
return false;
}
this->jsonTestFile = json_doc.object();
Related
In my project I need to upload a file and check that inside it there is an integer (positive or negative) for each line so that it can be represented within a vector.
Since I am working on QT and C++ I have considered two proposals, but both when I insert a number in the file for example "35.2" with a comma and I press the start button the program crashes.
Is there a better solution to mitigate the problem? I am attaching one of the solution that I was evaluating.
QFile file_in(file_name);
file_in.open(QIODevice::ReadOnly | QIODevice::Text);
QTextStream in(&file_in);
int elem;
int pos = 0;
QString s;
QIntValidator valid(-999, 999, this);
while(!in.atEnd())
{
in >> elem;
s = QString::number(elem);
if(!valid.validate(s,pos))
{
v.clear();
QMessageBox msg(this);
msg.setText("Input non valido");
msg.exec();
return;
}
else
{
v.push_back(elem);
}
}
A simpler solution would be to read the file line by line and check if the current line is convertible to an integer.
Qt already provides a lot of convenience methods. For instance, you would be interested by QByteArray::toInt().
Hence a solution could be:
bool validate(const QString & file_name)
{
QFile in_f(file_name);
if(!in_f.open(QIODevice::ReadOnly | QIODevice::Text))
return false;
bool is_valid = true;
while(is_valid && !in_f.atEnd())
{
QByteArray line = in_f.readLine();
line.toInt(&is_valid); // is_valid is set to false if the conversion failed
}
return is_valid;
}
This way there is no crash. If each line contains an integer, it will return true, otherwise, if at least one line contains something else (string, double, ...), it will return false.
As implemented here, if the file is empty, the function will return true. Of course you can add a check for this case if you want to return false instead.
Please read the doc about validate method here:
https://doc.qt.io/archives/qt-4.8/qintvalidator.html#validate
you are using a criteria for valid inputs as valid(-999, 999, this);
so if the list in the file is holding a row with 1000, using a toInt method will return you a false positive result!
the result is not just True or False like you may think...
you actually get:
I would like to add text to a text file only if the text doesn't already exist in the text file. My implementation below adds text even if it already exists. How can I fix my implementation to only add new non-existent items?
My implementation so far:
WriteToFile::WriteToFile(QString data)
{
path += "C:/Data.txt";
QFile file(path);
if ( file.open(QFile::Append) )
{
QTextStream in (&file);
QString line;
do {
line = in.readAll();
qDebug() << in.readLine();
if (!line.contains(data)) {
QTextStream stream( &file );
data += "\r\n";
stream << data << endl;
}
} while (!line.isNull());
}
}
You will either have to:
parse the entire file and extract all paths from it or
keep track of all paths written to a file to avoid parsing it again and again
From there is it simple, just create a QSet<QString> writtenSoFar, and for every path, check if the set contains it, if so skip writing, if not, write it and append it to the set. In the first case, you will have to write the parsed paths into the set just to make a single check, wildly inefficient, just like the parsing itself. So better keep track of the paths as you go.
The set is important to give you good lookup performance. It is quite fast, since it is hash based, it is essentially a value-less QHash.
I created a C++ application that reads in XML files with the RapidXML parser. At one XML file that was shaped exactly the same as another one that worked, the parser threw an error:
"expected <"
The last five characters before the error were from the closing tag of the root element, so the error happened at the end-of-file:
</UW>
What I suspect this error to be related to, is a whitespace skipping bug being an issue with RapidXML v1.12 (I am using v1.13). I used no parsing flags (doc.parse<0>(bfr);).
According to this site, the bug was believed to be caused by faulty implementation of the "parse_trim_whitespace" parse flag. A patch was provided on that site, but there also seemed to be a problem with that patch.
The following is the XML document that caused this error. What I also don't understand - besides the reason for the error - is why the error didn't happen parsing another file with content of the same fashion. My application also successfully parses several other files before that file.
<?xml version="1.0" encoding="UTF-8"?>
<UW>
<Bez>EV005</Bez>
<Herst>Trumpf</Herst>
<Gesw>16</Gesw>
<Rad>1.6</Rad>
<Hoehe>100</Hoehe>
<Wkl>30</Wkl>
<BgVerf>Freibiegen</BgVerf>
<MaxBel>50</MaxBel>
<Kontur>0</Kontur>
<Grafik>0</Grafik>
</UW>
Part of my application were the error occours (this is the inside of a loop):
// Get "Bezeichnung" attribute
attr = subnode->first_attribute("Bezeichnung");
if ( !attr ){ err(ERR_FILE_INVALID,"Werkzeuge.xml"); return 0; }
name = attr->value();
// Get file name/URL
string fileName = name;
fileName.append(".xml");
// Open file
ifstream werkzeugFile(concatURL(PFAD_WERKZEUGE,fileName));
if(!werkzeugFile.is_open()) { err(ERR_FILE_NOTFOUND,fileName); return 0; }
// Get length
werkzeugFile.seekg(0,werkzeugFile.end);
int len = werkzeugFile.tellg();
werkzeugFile.seekg(0,werkzeugFile.beg);
// Allocate buffer
char * bfr = new char [len+1];
werkzeugFile.read(bfr,len);
werkzeugFile.close();
// Parse
SetWindowText(hwndProgress,"Parsing data: Werkzeuge/*.xml");
btmDoc.parse<0>(bfr);
// Get type of tool & check validity
xml_node<> *rt_node = btmDoc.first_node();
if ( strcmp(rt_node->name(),"OW") == 0 ){
isOW = true;
}
else if ( strcmp(rt_node->name(),"UW") == 0 ){
isUW = true;
}
else { err(ERR_FILE_INVALID,fileName); return 0; }
// Prepare for next loop iteration
delete[] bfr;
btmDoc.clear();
subnode = subnode->next_sibling();
Ah, I think I see it. Two things:
First, the ifstream is suspicious -- shouldn't it be opened in binary mode if you're jumping around in it using byte offsets (and somebody else is doing the parsing)? Passstd::ios::in | std::ios::binary as the second argument to the ifstream constructor.
Second, your memory management seems fine, except that you allocate one byte extra (the +1) but never seem to make use of it. I'm assuming you're missing bfr[len] = '\0'; after the contents are read in -- this explains the odd parse error at the end of the file, since the XML parser doesn't know it reached the end of the file -- it's parsing a null terminated string that isn't null terminated, and tries to parse random bytes of memory ;-)
I need to read properties from a file to affect program behavior. Looks like boost::property_tree will do quite nicely. But, I'm wondering if when fetching different kinds of values that the library may reads the file multiple times?
For performance reason I'd like it to only be once. Most of the properties will be simple values like numbers, and strings. But occasionally there will be lists of numbers and lists strings.
I figure the it parses the file only once, but you never know and hence the question.
thanks.
You control it, and it reads only once:
//
// All that is required to read an xml file into the tree
//
std::ifstream in("region.xml");
boost::property_tree::ptree result_tree;
boost::property_tree::read_xml(in,result_tree);
Include the correct header and read ini, json or xml files into the tree using the corresponding read_XXX method.
You then use a "path" to access elements from the tree, or you can iterate over subtrees
BOOST_FOREACH(boost::property_tree::ptree::value_type &v,result_tree.get_child("gpx.rte"))
{
if( v.first == "rtept" ) //current node/element name
{
boost::property_tree::ptree subtree = v.second ;
//A path to the element is required to access the value
const int lat = sub_tree.get<double>( "<xmlattr>.lat")*10000.0;
const int lon = sub_tree.get<double>( "<xmlattr>.lon")*10000.0;
}
}
or direct access via a path:
// Here is simplistic access of the data, again rewritten for flexibility and
// exception safety in real code (nodes shortened too)
const int distVal =
result_tree.get<int>
( "Envelope.Body.MatrixResponse.Matrix.Route.<xmlattr>.distance")/1000;
Every time you want to read you can check if the file has changed. If yes, then you can read it.
char filename[] = "~/test.txt";
char timeStr[100] = "";
struct stat buf;
time_t ltime;
char datebuf[9];
char timebuf[9];
if (!stat(filename, &buf)) {
strftime(timeStr, 100, "%d-%m-%Y %H:%M:%S", localtime(&buf.st_mtime));
printf("\nLast modified date and time = %s\n", timeStr);
}
else {
printf("error getting atime\n");
}
_strtime(timebuf);
_strdate(datebuf);
printf("\nThe Current time is %s\n",timebuf);
printf("\nThe Current Date is %s\n",datebuf);
time(<ime);
if (difftime(ltime ,buf.st_mtime) > 0) {
// Read it.
}
I have a file named DBFile.
I am using the following code:
QString DBfile ="C:/Users/E543925/Desktop/VikuTB.xml";
QFile newFile(DBfile);
newFile.open( QIODevice::WriteOnly);
Now I want to write something inside the file if it is empty.
How can I check whether a file is empty or not in Qt?
Check file size before open by newFile.size()
add the append flag and check the insertion pointer:
newFile.open( QIODevice::WriteOnly|QIODevice::Append );
if (newFile.pos() == 0) {
// is empty
} else {
// some data inside
}
disclaimer: untested code, now i'll take the time to try it...
edit: tested, seems to work well...