RapidXML, reading and saving values - c++

I've worked myself through the rapidXML sources and managed to read some values. Now I want to change them and save them to my XML file:
Parsing file and set a pointer
void SettingsHandler::getConfigFile() {
pcSourceConfig = parsing->readFileInChar(CONF);
cfg.parse<0>(pcSourceConfig);
}
Reading values from XML
void SettingsHandler::getDefinitions() {
SettingsHandler::getConfigFile();
stGeneral = cfg.first_node("settings")->value();
/* stGeneral = 60 */
}
Changing values and saving to file
void SettingsHandler::setDefinitions() {
SettingsHandler::getConfigFile();
stGeneral = "10";
cfg.first_node("settings")->value(stGeneral.c_str());
std::stringstream sStream;
sStream << *cfg.first_node();
std::ofstream ofFileToWrite;
ofFileToWrite.open(CONF, std::ios::trunc);
ofFileToWrite << "<?xml version=\"1.0\"?>\n" << sStream.str() << '\0';
ofFileToWrite.close();
}
Reading file into buffer
char* Parser::readFileInChar(const char* p_pccFile) {
char* cpBuffer;
size_t sSize;
std::ifstream ifFileToRead;
ifFileToRead.open(p_pccFile, std::ios::binary);
sSize = Parser::getFileLength(&ifFileToRead);
cpBuffer = new char[sSize];
ifFileToRead.read( cpBuffer, sSize);
ifFileToRead.close();
return cpBuffer;
}
However, it's not possible to save the new value. My code is just saving the original file with a value of "60" where it should be "10".
Rgds
Layne

I think this is a RapidXML Gotcha
Try adding the parse_no_data_nodes flag to cfg.parse<0>(pcSourceConfig)

You should definitely be testing that the output file opened correctly and that your write succeeded. At the simplest, you need something like:
if ( ! ofFileToWrite << "<?xml version=\"1.0\"?>\n"
<< sStream.str() << '\0' ) {
throw "write failed";
}
Note that you don't need the '\0' terminator, but it shouldn't do any harm.

Use the following method to add an attribute to a node. The method uses the allocation of memory for strings from rapidxml. So rapidxml takes care of the strings as long as the document is alive. See http://rapidxml.sourceforge.net/manual.html#namespacerapidxml_1modifying_dom_tree for further information.
void setStringAttribute(
xml_document<>& doc, xml_node<>* node,
const string& attributeName, const string& attributeValue)
{
// allocate memory assigned to document for attribute value
char* rapidAttributeValue = doc.allocate_string(attributeValue.c_str());
// search for the attribute at the given node
xml_attribute<>* attr = node->first_attribute(attributeName.c_str());
if (attr != 0) { // attribute already exists
// only change value of existing attribute
attr->value(rapidAttributeValue);
} else { // attribute does not exist
// allocate memory assigned to document for attribute name
char* rapidAttributeName = doc.allocate_string(attributeName.c_str());
// create new a new attribute with the given name and value
attr = doc.allocate_attribute(rapidAttributeName, rapidAttributeValue);
// append attribute to node
node->append_attribute(attr);
}
}

Related

Quick way to extract the infomation from .xml files to the object

I am starter and right now I am trying to extract the key information from a .xml file then load them to an object of my class, for example:
Here are some information in .xml file:
<row Id="17" Phone="12468" Address="Bos" />
<row Id="242" Phone="98324" Address="Chi" Age="30"/>
<row Id="157" Phone="23268" Age="25" />
<row Id="925" Phone="54325" Address="LA" />
And my class would be:
class worker{
string ID;
string Phone;
string Address;
string Age;
}
I know the infomation would be various and if there is not that infomation of that line, we put ""(empty string) in it as return. And I know the infomation are given in the same order of the fields in class. I try to implement a function, let says extractInfo(const string& line, const string &key)
//#line: the whole line read from .xml
//#key: it would be "Id:"", "Phone:"", "Address:"" or "Age:"", so that I could reach the
// previous index of the infomation that I could extract.
extractInfo(const string& line, const string &key){
int index = line.find(key);
if(index == -1) return "";
int start = index + key.length(); //to reach the start quote
int end = start;
while(line[end] != '"'){ //to reach the end quote
end++;
}
return line.substr(start, end - start);
}
int main(){
...// for each line read from .xml, I build a new object of class worker and filling the field
worker.Id = extraInfo(line, "Id:\"");
worker.Phone = extraInfo(line, "Phone:\"");
...//etc.
...//then work on other manipulation
return 0;
}
My question are, is there any way that I could read and load the infomation from xml much more quickly through other APL or functions? That is, is there any way for me to improve this function when the .xml is a huge file with TBytes? And, is there any way that I can use less memory to, for example, find the oldest worker then print out? I know it's tough for me and I still try hard on it!
Thank all the ideas and advice in advance!
You can parse XML with existing XML parsing libraries, such as rapidxml, libxml2, etc.
Please note that for huge XML, since it need read all XML content to create the DOM tree, so the DOM method is not really suitable. you can use libxml2's xmlreader to parse each node one by one.
libxml2 xml reader
static void
streamFile(const char *filename) {
xmlTextReaderPtr reader;
int ret;
reader = xmlReaderForFile(filename, NULL, 0);
if (reader != NULL) {
ret = xmlTextReaderRead(reader);
while (ret == 1) {
const xmlChar *name = xmlTextReaderConstName(reader);
if(xmlStrEqual(BAD_CAST "row", name)) {
const xmlChar *id = xmlTextReaderGetAttribute(reader, "Id");
const xmlChar *phone = xmlTextReaderGetAttribute(reader, "Phone");
// you code here...
xmlFree(id);
xmlFree(phone);
}
ret = xmlTextReaderRead(reader);
}
xmlFreeTextReader(reader);
if (ret != 0) {
fprintf(stderr, "%s : failed to parse\n", filename);
}
} else {
fprintf(stderr, "Unable to open %s\n", filename);
}
}
And, If your XML format is always like above, you can also use std::regex_search to handle it
https://en.cppreference.com/w/cpp/regex/regex_search
#include <iostream>
#include <string>
#include <regex>
int main()
{
std::string str = R"(<row Id="17" Phone="12468" Address="Bos" />)";
std::regex regex("(\\w+)=\"(\\w+)\"");
// get all tokens
std::smatch result;
while (std::regex_search(str, result, regex))
{
std::cout << result[1] << ": " << result[2] << std::endl;
str = result.suffix().str();
}
}

allegro 5 writing files when using physfs

i am currently trying to figure out a way to write a file (an allegro configuration file to be exact) to a mounted zip-file using physfs and allegro 5.
reading the config file works fine, but when it comes to writing the changed config, nothing happens (e.g. the file is not re-written and thus remains in it's old state).
also, when not using physfs, everything works perfectly.
here's the code i use:
Game::Game(int height, int width, int newDifficulty)
{
PHYSFS_init(NULL);
if (!PHYSFS_addToSearchPath("Data.zip", 1)) {
// error handling
}
al_set_physfs_file_interface();
cfg = al_load_config_file("cfg.cfg");
if (cfg != NULL) // file exists, read from it
{
const char *score = al_get_config_value(cfg, "", "highScore");
highScore = atoi(score); // copy value
}
else // file does not exist, create it and init highScore to 0
{
cfg = al_create_config();
al_set_config_value(cfg, "", "highScore", "0");
highScore = 0;
al_save_config_file("cfg.cfg", cfg);
}
...
}
and in another function:
void Game::resetGame()
{
// high score
if (player->getScore() > highScore)
{
highScore = player->getScore();
// convert new highScore to char* that can be saved
stringstream strs;
strs << highScore;
string temp_str = strs.str();
char const* pchar = temp_str.c_str();
if (cfg != NULL) // file exists, read from it
{
al_set_config_value(cfg, "", "highScore", pchar);
al_save_config_file("cfg.cfg", cfg);
}
}
...
}
since the code works without physfs, i guess i handle the config file itself correctly.
any help would be highly appreciated!
cheers,
hannes
in the meantime, i solved the issue myself.
apparently, physfs has no ability to write to an archive.
therefore, i need to PHYSFS_setWriteDir("jhdsaf"), save the cfg-file in that folder and then replace the original zip-file by an updated version with the cfg-file, just before the game closes (after all resources are unloaded because the zip is otherwise still in use).
if anyone is interested in the code to do this, just reply to this post!
hannes

cocos2d-x JSON file parsing

I have a .json file, containing an array of dictionaries. Can you show me a good way of parsing it? I'm using the cocos2d-x 3.0-alpha version and the json classes, placed in the external/json directory.
I tried:
Array* items = Array::createWithContentsOfFile("test.json");
and
string fullPath = CCFileUtils::getInstance()->fullPathForFilename("test.json");
long bufferSize = 0;
const char* mFileData = (const char*)FileUtils::getInstance()->getFileData(fullPath.c_str(), "r", &bufferSize);
string clearData(mFileData);
size_t pos = clearData.rfind("}");
clearData = clearData.substr(0, pos+1);
string data = clearData.c_str();
log("%s", clearData.c_str());
Json::Value _root;
Json::Reader reader;
reader.parse(data, _root);
but none of them work - the first method returns an empty array, the second one results a _root variable, containing the whole json, but I can't make it into an array and create a separate dictionary object for each of the array's elements ( which is what I'm trying to do ).
Use JsonCPP you mentioned above but with CCFileUtils class
unsigned long filesize = 0;
std::string content;
std::string fullPath = "path relative to your androidmanifest.xml/index.json"
unsigned char* fileData = CCFileUtils::sharedFileUtils()->getFileData(fullPath.c_str(), "r", &filesize);
content.append((char*)fileData);
delete[] fileData;
Json::Value jsonresult;
Json::Reader reader;
bool parsingSuccessful = reader.parse( content, jsonresult );
if ( !parsingSuccessful )
{
// report to the user the failure
return false;
}

Create JSON array of strings with jsoncpp

I need to update an index (in JSON format) when writing a new file to disk, and since the files are categorized, I'm using an object with this kind of structure:
{ "type_1" : [ "file_1", "file_2" ], "type_2" : [ "file_3", "file_4" ] }
I thought it was an easy task for jsoncpp, but I'm probably missing something.
My code (simplified) here:
std::ifstream idx_i(_index.c_str());
Json::Value root;
Json::Value elements;
if (!idx_i.good()) { // probably doesn't exist
root[type] = elements = Json::arrayValue;
} else {
Json::Reader reader;
reader.parse(idx_i, root, false);
elements = root[type];
if (elements.isNull()) {
root[type] = elements = Json::arrayValue;
}
idx_i.close();
}
elements.append(name.c_str()); // <--- HERE LIES THE PROBLEM!!!
std::ofstream idx_o(_index.c_str());
if (idx_o.good()) {
idx_o << root;
idx_o.close();
} else {
Log_ERR << "I/O error, can't write index " << _index << std::endl;
}
So, I'm opening the file, reading JSON data works, if I can't find any, I create a new array, the problem is: when I try to append a value to the array, it doesn't work, the array remains empty, and is written to file.
{ "type_1" : [], "type_2" : [] }
Tried to debug my code, and the jsoncpp calls, and everything seems to be ok, but the array is always empty.
The problem arises here:
elements = root[type];
because you are creating a copy of root[type] when calling this JsonCpp API:
Value &Value::operator[]( const std::string &key )
thus not modifying root document at all. Simplest way to avoid this problem is, in your case, to not use the elements variable:
root[type].append(name.c_str());

Partial line from cpp file ending up in output file - haunted code?

I'm sorry, it would be extremely difficult to make a fully reproducible version of the error --- so please bare with my schematic code.
This program retrieves information from a web page, processes it, and saves output to an ASCII file. I also have a 'log' file (FILE *theLog---contained within a Manager object) for reporting errors, etc.
Some background methods:
// Prints string to log file
void Manager::logEntry(const string lstr) {
if( theLog != NULL ) { fprintf(theLog, "%s", lstr.c_str()); }
}
// Checks if file with given name already exists
bool fileExists(const string fname) {
FILE *temp;
if( temp = fopen(fname.c_str(), "r") ) {
fclose(temp);
return true;
} else { return false; }
}
// Initialize file for writing (some components omitted)...
bool initFile(FILE *&oFile, const string fname) {
if(oFile = fopen(fname.c_str(), "w") ) { return true; }
else { return false; }
}
The stuff causing trouble:
// Gets data from URL, saves to file 'dataFileName', input control flag 'foreCon'
// stu is some object that has string which i want
bool saveData(Manager *man, Stuff *stu, string dataFileName, const int foreCon) {
char logStr[CHARLIMIT_LARGE]; // CHARLIMIT_LARGE = 2048
sprintf(logStr, "Saving Data...\n");
man->logEntry( string(logStr) ); // This appears fine in 'theLog' correctly
string data = stu->getDataPrefixStr() + getDataFromURL() + "\n"; // fills 'data' with stuff
data += stu->getDataSuffixStr();
if( fileExists(dataFileName) ) {
sprintf(logStr, "save file '%s' already exists.", dataFileName.c_str() );
man->logEntry( string(logStr) );
if( foreCon == -1 ) {
sprintf(logStr, "foreCon = %d, ... exiting.", foreCon); // LINE 'A' : THIS LINE ENDS UP IN OUTPUT FILE
tCase->logEntry( string(logStr) );
return false;
} else {
sprintf(logStr, "foreCon = %d, overwriting file.", foreCon); // LINE 'B' : THIS LINE ENDS UP IN LOG FILE
tCase->logEntry( string(logStr) );
}
}
// Initialize output file
FILE *outFile;
if( !initFile(outFile, dataFileName) ) {
sprintf(logStr, "couldn't initFile '%s'", dataFileName.c_str());
tCase->logEntry( string(logStr) );
return false;
}
fprintf(outFile, "%s", data.c_str()); // print data to output file
if( fclose(outFile) != EOF) {
sprintf(logStr, "saved to '%s'", dataFileName.c_str());
tCase->logEntry( string(logStr) );
return true;
}
return false;
}
If the file already exists, AND 'int foreCon = -1' then the code should print out line 'A' to the logFile. If the file exists and foreCon != -1, the old file is overwritten with data. If the file doesn't exist, it is created, and the data is written to it.
The result however, is that a broken up version of line 'A' appears in the data file AND line 'B' is printed in the log file!!!!
What the data file looks like:
.. exiting.20130127 161456
20130127 000000,55,17,11,0.00
20130127 010000,54,17,11,0.00
... ...
The second line and onward look correct, but there is an extra line that contains part of line 'A'.
Now, the REALLY WEIRD PART. If I comment out everything in the if( foreCon == -1) { ... } block, then the data file looks like:
%d, ... exiting.20130127 161456
20130127 000000,55,17,11,0.00
20130127 010000,54,17,11,0.00
... ...
There is still an extra line, but it is the LITERAL CODE copied into the data file.
I think there is a poltergeist in my code. I don't understand how any of this could happen.
Edit: I've tried printing to console the data string, and it gives the same messed up values: i.e. %d, ... exiting.20130127 161456 - so it must be something about the string instead of the FILE *
Answer based on your latest comment:
getDataPrefixStr() ends up returning a string which starts with
something like string retStr = COMCHAR + " file created on ..."; such
that const char COMCHAR = '#';. Could the COMCHAR be the problem??
You can't add characters and string literals (which are arrays of char, not strings) like that.
You're adding 35 (the ASCII for "#") to the address of " file created on ... ", i.e. getDataPrefixStr() is whatever starts 35 characters from the start of that string. Since all literal strings are stored together in the same data area, you'll get strings from the program in the output.
Instead, you cold do
const string COMCHAR = "*";
string retStr = COMCHAR + " file created on ...";
It could be that logStr is too short and that it is causing data to be overwritten in other buffers (did you double check CHARLIMIT_LARGE?). You can diagnose this by commenting all writes to logStr (sprintf) and see if data is still corrupted. In general, your code is vulnerable to this if a user can set dataFileName (to be a very long string); use snprintf or ostringstream instead.
Otherwise, I would guess that either stu->getDataPrefixStr() or getDataFromURL() are returning corrupted results or return type char* instead of string. Try printing these values to the console directly to see if they are corrupted or not. If they return a char*, then data = stu->getDataPrefixStr() + getDataFromURL() will have undefined behavior.
if( temp = fopen(fname.c_str(), 'r') ) {
should be
if( temp = fopen(fname.c_str(), "r") ) {