I am trying to make a function in my application that can load in an object through attributes in an xml file. I would like to use TinyXML2 as I hear it is pretty easy and quick for games.
Currently I have the following xml file:
<?xml version="1.0" encoding="UTF-8"?>
<Level>
<Pulsator starttime="0" type="0" higherradius="100" lowerradius="10" time="60" y="500" x="300" bpm="60"/>
</Level>
Each attribute of the Pulsator is a variable in my Pulsator class. I use the followign function to import my Pulsators and add them to an vector of objects.
void Game::LoadLevel(string filename)
{
tinyxml2::XMLDocument level;
level.LoadFile(filename.c_str());
tinyxml2::XMLNode* root = level.FirstChild();
tinyxml2::XMLNode* childNode = root->FirstChild();
while (childNode)
{
Pulsator* tempPulse = new Pulsator();
float bpm;
float type;
std::string::size_type sz;
tinyxml2::XMLElement* data = childNode->ToElement();
string inputdata = data->Attribute("bpm");
bpm = std::stof(inputdata, &sz);
if (type == 0)
{
tempPulse->type = Obstacle;
tempPulse->SetColor(D2D1::ColorF(D2D1::ColorF::Black));
}
if (type == 1)
{
tempPulse->type = Enemy;
tempPulse->SetColor(D2D1::ColorF(D2D1::ColorF::Red));
}
if (type == 2)
{
tempPulse->type = Score;
tempPulse->SetColor(D2D1::ColorF(D2D1::ColorF::Green));
}
else
{
tempPulse->type = No_Type;
}
objects.push_back(tempPulse);
}
}
Every time I get to the root node, it loads in incorrectly and the childnode becomes null.
Am I using this incorrectly or is there an issue with my XML file?
The code doesn't correctly specify the child it wants. You want the first XMLElement, not the first child. To do that, use this code when you get the childNode:
tinyxml2::XMLElement* childNode = root->FirstChildElement();
And that saves you the cast later. (You don't need, and shouldn't use, the ToElement()).
Related
I can turn my XML document file into rapidxml object:
if(exists("generators.xml")) { //http://stackoverflow.com/a/12774387/607407
rapidxml::file<> xmlFile("generators.xml"); // Open file, default template is char
xml_document<> doc; // character type defaults to char
doc.parse<0>(xmlFile.data());; // 0 means default parse flags
xml_node<> *main = doc.first_node(); //Get the main node that contains everything
cout << "Name of my first node is: " << doc.first_node()->name() << "\n";
if(main!=NULL) {
//Get random child node?
}
}
I'd like to pick one random child node from the main object. My XML looks like this (version with comments):
<?xml version="1.0" encoding="windows-1250"?>
<Stripes>
<Generator>
<stripe h="0,360" s="255,255" l="50,80" width="10,20" />
</Generator>
<Generator>
<loop>
<stripe h="0,360" s="255,255" l="50,80" width="10,20" />
<stripe h="0,360" s="255,255" l="0,0" width="10,20" />
</loop>
</Generator>
</Stripes>
I want to pick random <Generator> entry. I think getting the child count would be a way to do it:
//Fictional code - **all** methods are fictional!
unsigned int count = node->child_count();
//In real code, `rand` is not a good way to get anything random
xmlnode<> *child = node->childAt(rand(0, count));
How can I get child count and child at offset from rapidxml node?
RapidXML stores the DOM tree using linked lists, which as you'll know are not directly indexable.
So you'd basically need to implement those two methods yourself, by traversing the nodes children. Something like this (untested) code.
int getChildCount(xmlnode<> *n)
{
int c = 0;
for (xmlnode<> *child = n->first_node(); child != NULL; child = child->next_sibling())
{
c++;
}
return c;
}
getChildAt is obviously similar.
I have a class in my program that uses Rapid XML to write data to file. This process works fine. However when I attempt to read the same data, my program will always be halted by internal error catching code, explaining "next sibling returned NULL but attempted to read value anyways".
if (xmlFile.good())
{
vector<char> buffer((istreambuf_iterator<char>(xmlFile)), istreambuf_iterator<char>());
buffer.push_back('\0');
doc.parse<0>(&buffer[0]);
root_node = doc.first_node("CityData");
for(xml_node<> * bound_node = root_node->first_node("Boundaries"); bound_node; bound_node = bound_node->next_sibling())
{
if (bound_node->first_attribute("enabled")->value() != NULL)
{
int enabled = atoi(bound_node->first_attribute("enabled")->value());
if (enabled == 1)
boundaries = true; // Program globals
}
}
if (boundaries)
{
for(xml_node<> * dimen_node = root_node->first_node("Dimensions"); dimen_node; dimen_node = dimen_node->next_sibling())
{
cityDim.x = atoi(dimen_node->first_attribute("x-val")->value()); // Program globals
cityDim.y = atoi(dimen_node->first_attribute("y-val")->value());
}
}
An example of how the data appears in the XML file:
<CityData version="1.0" type="Example">
<Boundaries enabled="1"/>
<Dimensions x-val="1276" y-val="688"/>
If I add a breakpoint before the either loop attempts to reiterate and look at the values, we can see they are read from the first iteration, however the end criteria for the loop appears to be incorrect and upon next_sibling() the error occurs. I cannot understand the issue, as the code for the loop was copied from an example completely unmodified (aside from variable renaming) and appears correct to me (modifying it to node != NULL) does not help.
In the bound_node-loop the variable bound_node first points to <Boundaries enabled="1"> and you are able to read the attribute with the name enabled. After the call to next_sibling(), bound_node points to <Dimensions .../> and the call to first_attribute("enabled") will return a null pointer because this xml-node does not have an attribute with this name and the subsequent call to value() will cause the program to crash.
I do not understand why you are writing a loop over all nodes. If the xml-file looks like this
<CityData version="1.0" type="Example">
<Boundaries enabled="1"/>
<Dimensions x-val="1276" y-val="688"/>
</CityData>
Then you can extract the values like this:
xml_node<> const * bound_node = root_node->first_node("Boundaries");
if (bound_node)
{
xml_attribute<> const * attr_enabled = bound_node->first_attribute("enabled");
if (attr_enabled)
{
int enabled = atoi(attr_enabled->value());
if (enabled == 1)
boundaries = true;
}
}
if (boundaries)
{
xml_node<> const * dimen_node = root_node->first_node("Dimensions");
if (dimen_node)
{
xml_attribute<> const * xval = dimen_node->first_attribute("x-val");
xml_attribute<> const * yval = dimen_node->first_attribute("y-val");
if (xval && yval)
{
cityDim.x = atoi(xval->value());
cityDim.y = atoi(yval->value());
}
}
}
}
You can write some else-clauses to signal errors, of course.
I am using jsoncpp to read settings from a JSON file.
I would like to have two cascading settings file, say MasterSettings.json and LocalSettings.json where LocalSettings is a subset of MasterSettings. I would like to load MasterSettings first and then LocalSettings. Where LocalSettings has a value that differs from MasterSettings, that value would overwrite the one from MasterSettings. Much like the cascade in CSS.
Is there any elegant way to do this with jsoncpp?
I'm going to assume your settings files are JSON objects.
As seen here, when JSONCpp parses a file, it clears the contents of the root node. This mean that trying to parse a new file on top of the old one won't preserve the old data. However, if you parse both files into separate Json::Value nodes, it's straight forward to recursively copy the values yourself by iterating over the keys in the second object using getMemberNames.
// Recursively copy the values of b into a. Both a and b must be objects.
void update(Json::Value& a, Json::Value& b) {
if (!a.isObject() || !b.isObject()) return;
for (const auto& key : b.getMemberNames()) {
if (a[key].isObject()) {
update(a[key], b[key]);
} else {
a[key] = b[key];
}
}
}
I know it has been a while. but...
In addition to the correct answer and the commentary, here is a code version for those who use a older g++ version:
void jsonMerge(Json::Value &a, Json::Value &b) {
if (!a.isObject() || !b.isObject()) return;
vector<string> member_name = b.getMemberNames();
string key = "";
for (unsigned i = 0, len = member_name.size(); i < len; i++) {
key = member_name[i];
if (!a[key].isNull() && a[key].type() == Json::objectValue && b[key].type() == Json::objectValue) {
jsonMerge(a[key], b[key]);
} else {
a[key] = b[key];
}
}
member_name.clear();
}
I decided to use libxml2 parser for my qt application and im stuck on xpath expressions. I found an example class and methods, and modified this a bit for my needs. The code
QStringList* LibXml2Reader::XPathParsing(QXmlInputSource input)
{
xmlInitParser();
xmlDocPtr doc;
xmlXPathContextPtr xpathCtx;
xmlXPathObjectPtr xpathObj;
QStringList *valList =NULL;
QByteArray arr = input.data().toUtf8(); //convert input data to utf8
int length = arr.length();
const char* data = arr.data();
doc = xmlRecoverMemory(data,length); // build a tree, ignoring the errors
if(doc == NULL) { return NULL;}
xpathCtx = xmlXPathNewContext(doc);
if(xpathCtx == NULL)
{
xmlFreeDoc(doc);
xmlCleanupParser();
return NULL;
}
xpathObj = xmlXPathEvalExpression(BAD_CAST "//[#class='b-domik__nojs']", xpathCtx); //heres the parsing fails
if(xpathObj == NULL)
{
xmlXPathFreeContext(xpathCtx);
xmlFreeDoc(doc);
xmlCleanupParser();
return NULL;
}
xmlNodeSetPtr nodes = xpathObj->nodesetval;
int size = (nodes) ? nodes->nodeNr : 0;
if(size==0)
{
xmlXPathFreeContext(xpathCtx);
xmlFreeDoc(doc);
xmlCleanupParser();
return NULL;
}
valList = new QStringList();
for (int i = 0; i < size; i++)
{
xmlNodePtr current = nodes->nodeTab[i];
const char* str = (const char*)current->content;
qDebug() << "name: " << QString::fromLocal8Bit((const char*)current->name);
qDebug() << "content: " << QString::fromLocal8Bit((const char*)current->content) << "\r\n";
valList->append(QString::fromLocal8Bit(str));
}
xmlXPathFreeObject(xpathObj);
xmlXPathFreeContext(xpathCtx);
xmlFreeDoc(doc);
xmlCleanupParser();
return valList;
}
As an example im making a request to http://yandex.ru/ and trying to get the node with class b-domik__nojs which is basically one div.
xpathObj = xmlXPathEvalExpression(BAD_CAST "//[#class='b-domik__nojs']", xpathCtx); //heres the parsing fails
the problem is the expression //[#class='b-domik__nojs'] doesn't work at all. I checked it in firefox xpath ext., and in opera developer tools xpath ext. in there this expression works perfectly.
I also tried to get other nodes with attributes but for some reason xpath for ANY attribute fails. Is there something wrong in my method? Also when i load a tree using xmlRecover, it gives me a lot of parser errors in debug output.
Ok i played a bit with my libxml2 function more and used "//*" expression to get all elements in the document, but! It returns me only the elements in the first children node of the body tag. This is the yandex.ru dom tree
so basically it gets ALL the elements in the first div "div class="b-line b-line_bar", but doesnt look for the other elements in other child nodes of the <body> for some reason.
Why can that happen? Maybe xmlParseMemory doesnt build a full tree for some reason? Is there any possible solution to fix this.
It is really strange that the expression works anywhere, because it is not a valid XPath expression. After the axis specification (//), there should be a nodetest (element name or *) before the predicate (the condition in square brackets).
//*[#class='bdomik__nojs']
Allright it works now, if my mistake was to use xml functions to make html documents into a tree. I used htmlReadMemory and the tree is fully built now. Some code again
xmlInitParser();
xmlDocPtr doc;
xmlXPathContextPtr xpathCtx;
xmlXPathObjectPtr xpathObj;
QByteArray arr = input.data().toUtf8();
int length = arr.length();
const char* data = arr.data();
doc = htmlReadMemory(data,length,"",NULL,HTML_PARSE_RECOVER);
if(doc == NULL) { return NULL;}
xpathCtx = xmlXPathNewContext(doc);
if(xpathCtx == NULL)
{
xmlFreeDoc(doc);
xmlCleanupParser();
return NULL;
}
xpathObj = xmlXPathEvalExpression(BAD_CAST "//*[#class='b-domik__nojs']", xpathCtx);
etc.
So, in RapidXML, I'm trying to loop through my file to get the data from some tileset nodes:
rapidxml::xml_node<> *root_node = doc.first_node("map");
for(rapidxml::xml_node<> *tileset = root_node->first_node("tileset");
tileset != 0; tileset = tileset->next_sibling("tileset"))
{
// Iteration stuff...
You're probably saying, what's the problem? Well, in RapidXML, the next_sibling() function optionally matches the name:
xml_node<Ch>* next_sibling(const Ch *name=0, std::size_t name_size=0, bool
case_sensitive=true) const;
Gets next sibling node, optionally matching node name. Behaviour is undefined
if node has no parent. Use parent() to test if node has a parent.
Hence, if a node is not found with the name, it'll just return the next sibling regardless. This is a problem in my program, and I just plain don't want the extra iteration. I think this is stupid, but whatever. Is there a way to make it ONLY iterate through my tileset nodes?
"optionally matching node name" - As in the parameter is optional. If you pass a name string, and it is not found you will get a return value of zero.
xml_node<Ch> *next_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
{
assert(this->m_parent); // Cannot query for siblings if node has no parent
if (name)
{
if (name_size == 0)
name_size = internal::measure(name);
for (xml_node<Ch> *sibling = m_next_sibling; sibling; sibling = sibling->m_next_sibling)
if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive))
return sibling;
return 0;
}
else
return m_next_sibling;
}
I also had this problem and I used this small modification as a workaround, which works as intended.
rapidxml::xml_node<> *root_node = doc.first_node("map");
for(rapidxml::xml_node<> *tileset = root_node->first_node("tileset");
tileset != 0;
tileset = tileset->next_sibling())
{
if(strcmp(tileset->name(), "tileset")!=0)
continue;
//TODO: the usual loop contents
}