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;
}
Related
I'm working on making some Spotify API calls on an ESP32. I'm fairly new to C++ and while I seem to got it working how I wanted it to, I would like to know if it is the right way/best practice or if I was just lucky. The whole thing with chars and pointers is still quite confusing for me, no matter how much I read into it.
I'm calling the Spotify API, get a json response and parse that with the ArduinoJson library. The library returns all keys and values as const char*
The library I use to display it on a screen takes const char* as well. I got it working before with converting it to String, returning the String with the getTitle() function and converting it back to display it on screen. After I read that Strings are inefficient and best to avoid, I try to cut out the converting steps.
void getTitle()
{
// I cut out the HTTP request and stuff
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, http.getStream(), );
JsonObject item = doc["item"];
title = item["name"]; //This is a const char*
}
const char* title = nullptr;
void loop(void) {
getTitle();
u8g2.clearBuffer();
u8g2.setDrawColor(1);
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(1, 10, title);
u8g2.sendBuffer();
}
Is it okay to do it like that?
This is not fine.
When seeing something like this, you should immediately become suspicious.
This is because in getTitle, you are asking a local object (item) for a pointer-- but you use the pointer later, when the item object no longer exists.
That means your pointer might be meaningless once you need it-- it might no longer reference your data, but some arbitrary other bytes instead (or even lead to crashes).
This problem is independent of what exact library you use, and you can often find relevant, more specific information by searching your library documentation for "lifetime" or "object ownership".
FIX
Make sure that item (and also DynamicJsonDocument, because the documentation tells you so!) both still exist when you use the data, e.g. like this:
void setTitle(const char *title)
{
u8g2.clearBuffer();
u8g2.setDrawColor(1);
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(1, 10, title);
u8g2.sendBuffer();
}
void updateTitle()
{
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, http.getStream(), );
JsonObject item = doc["item"];
setTitle(item["name"]);
}
See also: https://arduinojson.org/v6/how-to/reuse-a-json-document/#the-best-way-to-use-arduinojson
Edit: If you want to keep parsing/display update decoupled
You could keep the JSON document "alive" for when the parsed data is needed:
/* "static" visibility, so that other c/cpp files ("translation units") can't
* mess mess with our JSON doc directly
*/
static DynamicJsonDocument doc(1024);
static const char *title;
void parseJson()
{
[...]
// super important to avoid leaking memory!!
doc.clear();
DeserializationError error = deserializeJson(doc, http.getStream(), );
// TODO: robustness/error handling (e.g. inbound JSON is missing "item")
title = doc["item"]["name"];
}
// may be nullptr when called before valid JSON was parsed
const char* getTitle()
{
return title;
}
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 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;
}
I have a rather large code for the data analysis software Root (CERN) and I have a run of data that I want to look through for bad runs. I have them all in one directory, but want to write a segment of code to take one file out of this folder at a time, run the code, output the resulting graphs, then take the next file.. etc. I am using a macro to run this code as it is now. I am hoping to just add something to that macro. I am somewhat novice to programming.
gSystem->Load("AlgoCompSelector_C.so");
// make the chains
std::string filekey;
TChain tree1 = new TChain("tree");
filekey = std::string("data/run715604.EEmcTree_Part1.root");
tree1->Add( filekey.data() );
To do this in a single root macro, you can try something like the code snippet below. here I add the files to a TChain but you could of course replace the TChain::Add with whatever you want.
int addfiles(TChain *ch, const char *dirname=".", const char *ext=".root")
{
int added = 0;
TSystemDirectory dir(dirname, dirname);
TList *files = dir.GetListOfFiles();
if (files) {
TSystemFile *file;
TString fname;
TIter next(files);
while ((file=(TSystemFile*)next())) {
fname = file->GetName();
if (!file->IsDirectory() && fname.EndsWith(ext)) {
ch->Add(fname); // or call your function on this one file
++added;
}
}
}
return added;
}
(Adapted from this root-talk post: http://root.cern.ch/phpBB3/viewtopic.php?f=3&t=13666)
Having said that I think the suggestion by #m0skit0 to launch a smaller script each time is a better one than doing what you propose to do above. Root is finicky and having smaller jobs is better.
I am having a lot of trouble working with the libxml2 library to parse an xml file.
I have weeded out a previous, similar problem, but have run into another.
Here is the problem code:
class SSystem{
public:
//Constructors
SSystem(){};
//Make SSystem from XML Definition. Pass ptr to node
SSystem(xmlNodePtr Nptr, xmlDocPtr Dptr){
name = wxString((char *)xmlGetProp(Nptr, (xmlChar*)"name"), wxConvUTF8);
//Move to next level down, the <general> element
Nptr = Nptr->xmlChildrenNode;
//Move one more level down to the <radius> element
Nptr = Nptr->xmlChildrenNode;
//Get Radius value
if (!xmlStrcmp(Nptr->name, (const xmlChar *)"radius")) {
char* contents = (char*)xmlNodeGetContent(Nptr);
std::string test1 = std::string(contents);
radius = wxString(contents, wxConvUTF8);
}
}
Both an xmlNodePtr and an xmlDocPtr are passed to the constructor, which works fine taking just a property ("name"), but is now choking on further parsing.
Here is a piece of the xml file in question:
<?xml version="1.0" encoding="UTF-8"?>
<Systems>
<ssys name="Acheron">
<general>
<radius>3500.000000</radius> <-- I am trying to get this value (3500).
<stars>300</stars>
<asteroids>0</asteroids>
<interference>0.000000</interference>
<nebula volatility="0.000000">0.000000</nebula>
</general>
It compiles fine, but crashes when the constructor is loaded (I know because, if I comment out the if conditional and the char* contents = (char*)xmlNodeGetContent(Nptr->xmlChildrenNode), it runs fine.
I've tried so many different things (removed one of the Nptr->xmlChildrenNode), but nothing works.
What is wrong?
This:
char* contents = (char*)xmlNodeGetContent(Nptr->xmlChildrenNode)
Should probably be this:
char* contents = (char*)xmlNodeGetContent(Nptr)
Okay, I am going to use a different XML parsing library, as Libxml is a bit too complicated for me.
I am looking into using MiniXML (http://www.minixml.org/).
#Biosci3c:
The method you are calling returns some fake value. You should not call the method
char*)xmlNodeGetContent(Nptr->xmlChildrenNode)
instead you have to get the data corresponding to radius in cdata callback method below here.
void cdataBlock (void * ctx,
const xmlChar * value,
int len)
Check out in libxml library documentation for reference...
I just wrote a C++ wrapper to libxml2. It is on github if someone is interested: https://github.com/filipenf/libxml-cpp-wrapper
The idea is to make the use of libxml2 easier for C++ programmers - that's the main goal of this wrapper.
In the github repository there is a simple example of how to use it, but you can use it like this:
string office_phone = reader.getNodes()[0]["Customer"]["ContactInfo"]["OfficePhone"].text;
It is a work-in-progress so there is many room for improvement....