I have been trying to parse an XML file using boost's property tree, but every time I want to get the value of a string it throws an access violation exception. It works fine with integers so I'm a bit confused.
Here's some of the code:
class Config
{
char * test;
int test2;
public:
Config();
};
Config::Config(void)
{
boost::property_tree::ptree pt;
boost::property_tree::xml_parser::read_xml("config.xml", pt);
try
{
test = pt.get<char*>("base.char");
test2 = pt.get<int>("base.int");
}
catch(std::exception e)
{
//something wasn't specified
}
}
And the XML file:
<base>
<char>test</char>
<int>10</int>
</base>
First I thought it's because I didn't allocate space for the string but neither malloc() nor new char[] helped.
Any help would be appreciated. Thanks in advance :)
Based on this tutorial I think you need to use std::string instead of char* to get string values.
So the line test = pt.get<char*>("base.char"); would then be test = pt.get<std::string>("base.char");. (Assuming you change test's type to std::string as well).
Related
I've written a Google Test like the one below which compares some computed values with the one expected stored in a CSV file.
class SampleTest : public ::testing::Test{
public:
void setupFile(const std::string& filename) {
// open csv file here
}
void checkRow(ComputedRowValue val) {
CSVParsedOutput out;
m_csv_f.readAndParseLine(out);
EXPECT_EQ(out.field1, val.field1);
EXPECT_EQ(out.field2, val.field2);
EXPECT_EQ(out.field3, val.field3);
m_csv_line++;
}
protected:
CSVFile m_csv_f; // CSV file with expected results
int m_csv_line = 0;
};
This is going to be running across some huge file sizes and EXPECT_EQ when failing will only tell me which value mismatches. How can I override the error message output by EXPECT_EQ to also print m_csv_line?
You can use EXPECT_EQ as a stream so:
EXPECT_EQ(out.field1, val.field1) << m_csv_line;
should do what you want.
If you have multiple assertions within single check consider using SCOPED_TRACE macro, described here.
And instead of
EXPECT_EQ(out.field1, val.field1) << "text";
EXPECT_EQ(out.field2, val.field2) << "text";
EXPECT_EQ(out.field3, val.field3) << "text";
you can get
SCOPED_TRACE("text");
EXPECT_EQ(out.field1, val.field1);
EXPECT_EQ(out.field2, val.field2);
EXPECT_EQ(out.field3, val.field3);
That is even more helpful when you have complex output, like
EXPECT(...)<<"text"<<custom_class_var<<int_var;
EXPECT(...)<<"text"<<custom_class_var<<int_var;
EXPECT(...)<<"text"<<custom_class_var<<int_var;
It can be replaced with
std::stringstream message;
message<<"text"<<custom_class_var<<int_var;
SCOPED_TRACE(message.str());
EXPECT(...);
EXPECT(...);
EXPECT(...);
I'm facing this issue on an ESP8266 (Arduino like board), but this problem is regarding c/c++, so I'm asking this here.
I have not that much experience with native languages like c/c++ and I'm facing a strange issue, which drives me crazy. So I'm using an Wemos D1 mini (ESP8266) which uses a class calles ConfigManager to read a configuration file from eeprom. The config file is formatted as json, so I'm using ArduinoJson to parse the content. I have declared a StaticJsonBuffer and pointer to a JsonObject in the header, like you can see in the code example:
//FILE: ConfigManager.h
#ifndef ConfigManager_H
#define ConfigManager_H
#include <Arduino.h>
#include <ArduinoJson.h>
#include <FS.h>
#include "Logger.h"
class ConfigManager {
public:
Settings *settings;
ConfigManager();
void read_from_eeprom();
private:
File configFile;
JsonObject *json;
StaticJsonBuffer<200> jsonBuffer;
void open_file(const char *permission);
void read_json();
void recreate_file();
void create_json();
void check_success();
void populate_settings();
void clean_up();
};
#endif
When the function read_from_eeprom is invoked, it opens the file and invokes the functionread_json:
void ConfigManager::read_json() {
size_t size = configFile.size();
Log.verbose("[ConfigManager] Config file size: %d", size);
std::unique_ptr<char[]> buf(new char[size]);
configFile.readBytes(buf.get(), size);
Log.verbose("[ConfigManager] File content: %s", buf.get());
Log.verbose("[ConfigManager] Parsing json");
json = &jsonBuffer.parseObject(buf.get());
Log.notice("[ConfigManager] Json is:");
json->printTo(Serial);
Serial.println();
}
Which is followed by a call to check_success()
void ConfigManager::check_success() {
Log.notice("[ConfigManager] Json is:");
json->printTo(Serial);
Serial.println();
bool should_recreate = true;
if (json->success()) {
Log.notice("[ConfigManager] Parsed json successfully");
auto version = json->get<const char*>("version");
if (version) {
if (strcmp(version, Settings::current_version) == 0) {
Log.notice("[ConfigManager] Config version is up2date");
should_recreate = false;
} else {
Log.warning("[ConfigManager] Config version outdated");
}
} else {
Log.warning("[ConfigManager] Invalid config file");
}
} else {
Log.warning("[ConfigManager] Config file is not valid json");
}
if (should_recreate) {
Log.notice("[ConfigManager] Recreating config file");
recreate_file();
create_json();
}
Log.notice("JSON IS: ");
json->prettyPrintTo(Serial);
Log.notice("[ConfigManager] Sucessfully read json");
}
So what I noticed is that the file content is fine. E.g. {"version":"0.2","led_count":"64"}.
Then the json is parsed, which succeeds and logs the json object, which is again {"version":"0.2","led_count":"64"}.
Afterwards the function returns, and calls check_success, which again prints the content of the json object to the log, but this time it seems that something has overwritten the JsonBuffer, which causes the json object to be corrupted. This time the logged content is {"v␂":"0.2","led_count":"64"} (with some strange unicorn characters that change as the source code changes). I'm trying to figure out whats going on for many hours now, but I'm stuck. Can someone please point me in the right direction to solve this problem? Thank you!
The full Log can be found HERE, as well as ConfigManager.h and ConfigManager.cpp
*I'd prefer write that in comments, because I don't have arduino and can't verify that my advice 100% helpful. But I can't use comments with "my reputation" :). So please don't press "minus button" if my answer didn't help... *
According to that it seems you need to keep original json string while you using json buffer.
Keep the JSON string in memory long enough
The library never make memory duplication. This has an important implication on string
values, it means that the library will return pointer to chunks of the
string.
For instance, let’s imagine that you parse ["hello","world"], like
this:
char[] json = "[\"hello\",\"world\"]";
StaticJsonBuffer<32> buffer;
JsonArray& array = buffer.parseArray(json);
const char* first = array[0];
const char* second = array[1];
In that
case, both first and second are pointers to the content of the
original string json. So this will only work if json is still in
memory.
So, it make sense try make std::unique_ptr buf a class member (same as StaticJsonBuffer) and check how it works.
BTW, IMO std::vector will be more suitable there... And I'm not sure that unique_ptr deletes arrays properly.
I want to parse a simple text file and create an object from the data it contains. I'm using C++11 for this (and I'm not fluent).
In case of any kind of error (e.g. missing file or invalid text) I wish to tell the caller of my parsing function what went wrong, providing information like what kind of error occurred and where in the file.
I don't consider exceptional that errors occur while parsing, so it seems exceptions are not the way to go.
I thought of returning a struct with all the info, including the resulting parsed object in case of success:
struct ParsingResult
{
bool success;
int errorCode;
int errorLine;
ParsedObject object;
}
However I'm not convinced by this solution because, in case of errors, I must still provide a ParsedObject. I can define a default constructor for that, of course, but by it's nature a ParsedObject makes sense only when the parsing is successful.
I could change ParsedObject to ParsedObject*, but I'm reluctant to use pointers when not necessary, and I wonder if this can be avoided.
My question: can you suggest a better solution to this problem? What is it?
struct Obj
{
// your object from the data...
}
struct ParseError
{
int errorCode;
int errorLine;
}
class Parser
{
ParseError m_error;
// other things
public:
bool parse(const std::string& filename, Obj& parsedObject)
{
// Open file, etc...
//parsedObject.property1 = some_value;
// if something is wrong, set m_error and return false;
// return true if ok
}
ParseError getLastError() { return m_error; }
}
// in your code
Parser p;
Obj o;
if(!p.parse("filename", o))
{
// ParseError = p.getLastError();
}
You can use a
unique_ptr<ParsedObject>
or
shared_ptr<ParsedObject>
whose default constructor can be compared to null in case of unsuccessful parsing.
Avoiding raw pointers will free you from having to free memory :)
I was not allowed to create the new tag 'tinyxml2', that's why I am using the tag 'tinyxml', however I am using 'tinyxml2' !
I am trying to insert a subtree element to an existing XML file. My problem is, that after running the program and checking the XML file the subtree simply does not exist within the document. In the original code I am also checking for errors while loading and saving the file so there is no problem with these functions, they are working correctly. I tried a few different approaches and also adding a single element by using the UserList.NewElement(*name*)-function does also work fine.
Now I want to insert a whole subtree from a text variable...
My latest approach looks like this (simplified without checking LoadFile and SaveFile):
tinyxml2::XMLDocument UserList;
UserList.LoadFile(*Path*);
const char* XMLText = "<user name=\"test-user\" gender=\"male\"><ability description=\"I_can_do_magic\" /></user>";
tinyxml2::XMLDocument TestParse;
TestParse.Parse(XMLText);
tinyxml2::XMLElement* myNewUser = TestParse.RootElement();
UserList.FirstChildElement( "magicians" )->InsertEndChild(myNewUser);
UserList.SaveFile(*Path*);
By the way...
When I tried to parse my XMLText by using the tinyxml2::XMLDocument UserList the saved XML file will be empty after running the program. This means neither the original XML Document content, nor the newly parsed subtree will be saved when trying to do this. This fact made me use the second tinyxml2::XMLDocument TestParse. Now the XML file is saved containing it's original content, however the parsed subtree is still missing... thank you very much for any solution / help / advice.
TinyXML-2 allocates memory for its nodes (XMLNode) in memory pools stored in the XMLDocument. This fixes the memory fragmentation problems present in TinyXML-1.
The side effect is that elements can not be moved from one XMLDocument to another. They can only be copied. Regrettably, TinyXML-2 doesn't currently support deep copies (tree copies), so can't do what you want. (Although a deep copy is a requested on the github site.)
I would expect the code you wrote to assert (in debug mode) or crash, by the way, since myNewUser is in a different memory pool from UserList.
I wrote a deep copy function using XMLVisitor of TinyXML-2. Hopefully this is useful for you:
#include <stack>
#include "tinyxml2.h"
using namespace tinyxml2;
class MyXMLVisitor: public XMLVisitor
{
public:
MyXMLVisitor(XMLDocument *doc)
: m_doc(doc)
{
}
virtual bool VisitEnter (const XMLElement &el, const XMLAttribute *attr)
{
XMLElement *new_el = m_doc->NewElement(el.Name());
m_elementStack.push(new_el);
return true;
}
virtual bool Visit(const XMLText &txt)
{
m_elementStack.top()->SetText(txt.Value());
return true;
}
virtual bool VisitExit (const XMLElement &el)
{
XMLElement *top_el = m_elementStack.top();
m_elementStack.pop();
if (m_elementStack.empty()) {
m_element = top_el;
return false;
}
else {
m_elementStack.top()->InsertEndChild(top_el);
return true;
}
}
std::stack<XMLElement*> m_elementStack;
XMLDocument *m_doc;
XMLElement *m_element;
};
XMLElement* DeepCopyElement(XMLDocument &doc, const XMLElement *el)
{
MyXMLVisitor my_visitor(&doc);
el->Accept(&my_visitor);
return my_visitor.m_element;
}
int main(int argc, char* argv[])
{
XMLDocument doc;
doc.LoadFile( "test.xml" );
XMLElement *modulesElement = doc.FirstChildElement("modules");
XMLElement *moduleElement = modulesElement->FirstChildElement("module");
modulesElement->InsertEndChild(DeepCopyElement(doc, moduleElement));
doc.SaveFile("test_out.xml");
return 0;
}
I am trying to create a configuration class which reads the data from an xml with rapidxml.
Therefore I have a private xml_document which I parse inside of the constructor:
class Config
{
public:
Config();
~Config();
/*returns the first found node*/
xml_node<>* findNode(const char* name);
/*get an configuration value. It's always a char*/
char* getConfigValue(const char* name);
private:
rapidxml::xml_document<>* m_doc;
};
//cpp
Config::Config()
{
m_doc = new xml_document<>();
rapidxml::file<> xmlFile("config.xml");
m_doc->parse<0>(xmlFile.data());
//LOG(getConfigValue("width")); // here it works
}
xml_node<>* Config::findNode(const char* name)
{
return m_doc->first_node()->first_node(name);
}
char* Config::getConfigValue(const char* name){
return m_doc->first_node()->first_node(name)->value();
}
Inside of the wWinMain I create an config opject and try to call the methods.
Config* config = new Config();
LOG(config->findNode("width")->value()); // this does create violation reading
BUT if I put the same line into the constructor of the Config class it works without any problem. What is going wrong here?
I'm not familiar with rapid xml, but a quick google search told me:
http://rapidxml.sourceforge.net/manual.html
RapidXml is an in-situ parser, which allows it to achieve very high
parsing speed. In-situ means that parser does not make copies of
strings. Instead, it places pointers to the source text in the DOM
hierarchy.
3.1 Lifetime Of Source Text
In-situ parsing requires that source text lives at least as long as
the document object. If source text is destroyed, names and values of
nodes in DOM tree will become destroyed as well. Additionally,
whitespace processing, character entity translation, and
zero-termination of strings require that source text be modified
during parsing (but see non-destructive mode). This makes the text
useless for further processing once it was parsed by RapidXml.
So what I take from that is that you can't just have rapidxml::file<> xmlFile("config.xml"); be a stack variable as xml_document->parse() will create a tree that points directly at memory in xmlFile. So xmlFile has to be in memory for at least as long as the xml_document otherwise you will have invalid data. This means that xmlFile needs to be a member of your Config class. Also, I don't see you initializing m_doc, did you just omit some code? Otherwise it should be complaining sooner and never work. Also, you should always check to make sure functions that return pointers aren't returning null before you access them to avoid getting reading violations.
So what you really want is something like this:
class Config
{
public:
Config();
~Config();
/*returns the first found node*/
xml_node<>* findNode(const char* name);
/*get an configuration value. It's always a char*/
char* getConfigValue(const char* name);
private:
rapidxml::file<> m_xmlFile;
rapidxml::xml_document<> m_doc;
};
//cpp
Config::Config()
: m_xmlFile("config.xml")
{
m_doc.parse<0>(m_xmlFile.data());
}
xml_node<>* Config::findNode(const char* name)
{
if (m_doc.first_node())
{
return m_doc.first_node()->first_node(name);
}
return 0;
}
char* Config::getConfigValue(const char* name)
{
if (m_doc.first_node())
{
xml_node<>* node = m_doc.first_node()->first_node(name);
if (node)
{
return node->value();
}
}
return 0;
}