Get child count from rapidxml:xmlnode to get random child node - c++

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.

Related

Why can't I copy the contents of an XMLDocument to another XMLDocument using TinyXml2?

I do not understand why the below code doesn't work as intended to copy the one element from doc1 to doc2:
void test_xml(){
using namespace tinyxml2;
XMLDocument doc1, doc2;
XMLPrinter printer;
doc1.LoadFile("./res/xml/basic.xml");
if(doc1.Error())
std::cout << doc1.ErrorName();
doc1.Print(&printer);
std::cout << printer.CStr(); // prints "</atag>" without quotes
printer.ClearBuffer();
doc2.InsertFirstChild(doc1.RootElement());
if(doc2.Error())
std::cout << doc2.ErrorName(); // doesn't run, there's no error
doc2.Print(&printer);
std::cout << printer.CStr(); // prints nothing, no child got inserted to doc2
std::cout << doc2.NoChildren(); //prints "1" meaning we didn't insert anything
}
Can someone point out how this can be improved?
From the TinyXml2 documentation:
InsertFirstChild ... Returns the addThis argument or 0 if the node does not belong to the same document.
Basically you can only add a node to a document if the node was manufactured by that document (with NewElement, NewText etc).
You have to walk through doc1 creating corresponding nodes as you go (with ShallowClone, and adding them to doc2. It appears there is no DeepClone to do it all for you.
At http://sourceforge.net/p/tinyxml/discussion/42748/thread/820b0377/, "practisevoodoo" suggests:
XMLNode *deepCopy( XMLNode *src, XMLDocument *destDoc )
{
XMLNode *current = src->ShallowClone( destDoc );
for( XMLNode *child=src->FirstChild(); child; child=child->NextSibling() )
{
current->InsertEndChild( deepCopy( child, destDoc ) );
}
return current;
}

Builder C++: XMLDocument Replace Node

I need to replace all nodes with others nodes.
My current node:
<str name="author">Brad Mc</str>
I need to replace it with this node:
<author>Brad Mc<author>
I have this code to replace all nodes with others nodes:
IXMLNode* xResultNode = XMLDocument1->DocumentElement->ChildNodes->FindNode("result");
IXMLNode* xDocNode;
IXMLNode* xFieldNode;
IXMLNode* xNewFieldNode;
// <result>
for (int i = 0; i < xResultNode->ChildNodes->Count - 1; i) {
// <doc>
xDocNode = xResultNode->ChildNodes->Get(i);
int count = xDocNode->ChildNodes->Count;
for (int j = 0; j < count - 1; j++) {
// <field>
xFieldNode = xDocNode->ChildNodes->Get(j);
String FieldName = xFieldNode->Attributes["name"];
String FieldText = xFieldNode->Text;
// Create new Node / modify node
xNewFieldNode = xDocNode->AddChild(FieldName);
xNewFieldNode->SetText(FieldText);
// I need to replace xFieldNode with xNewFieldNode
// how to do that?
}
}
XMLDocument1->SaveToFile("./ResponseOutPut.xml");
First, this code is full of memory leaks and invalid memory access. IXMLNode is a reference counted interface, but you are not managing the reference counts correctly. You need to replace IXMLNode* with _di_IXMLNode and let it manage the reference counts for you.
Second, to replace an entire node with a new node, you can use the parent node's ChildNodes->ReplaceNode() method. You can use the owning document's CreateNode() method to create the new node.

Issues loading data in from xml using TinyXml2

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()).

Replace instead of add a xml node

In this simple code after invoking second(), 1.xml has only one node "1". Why pugi replaces node and what should I do for correct modifying?
void first()
{
pugi::xml_document document;
pugi::xml_parse_result result = document.load_file("C:\\1.xml", parse_full);
pugi::xml_node node = document.append_child("0");
node.append_attribute("message") = "something";
document.save_file("C:\\1.xml");
}
void second()
{
pugi::xml_document document;
pugi::xml_parse_result result = document.load_file("C:\\1.xml", parse_full);
pugi::xml_node node = document.append_child("1");
node.append_attribute("message") = "something else";
document.save_file("C:\\1.xml");
}
void test()
{
first();
second();
}
You should check the result of load_file.
Here's what's going on here:
XML tag names can't start with digits. This is defined by XML standard.
pugixml performs this check while loading the document - so load_file() fails, producing an empty document
pugixml does not perform this check while appending nodes or saving document, so it is possible to save an invalid document

How to read Boost property_map with peer values with identical tag names

I am using the property_map from Boost c++ library v1.53, and its working great for me except I can't figure out how to parse data nodes with the same name that are peers of each other. As in the following XML:
<RECORDSET>
<C>
<CY>
<CZ>
<I>1</I>
<CT>10</CT>
</CZ>
<CZ>
<I>2</I>
<CT>12</CT>
</CZ>
</CY>
<CS>
<I>1</I>
<I>2</I>
</CS>
</C>
</RECORDSET>
I can parse everything above except the "I" data node elements under the "CS" tag at the bottom. I am trying to use the code:
// (works no problem)
BOOST_FOREACH(const ptree::value_type & vpairC, proptreeCs.get_child(string("C")))
{
if (vpairC.first.data() != std::string("C"))
continue;
// grab the "C" ptree sub-tree for readability.
ptree proptreeC = vpairC.second;
// (this works no problem to iterate through the multiple CZ nodes under CY)
// RM_CZs
short nCZCount = 0;
sTagName = ;
BOOST_FOREACH(const ptree::value_type & vpairCZ, proptreeC.get_child("C"))
{
// get a local ptree for readability.
ptree ptreeCZ = vpairCZ.second;
// get the I and CT ids.
sTagName = "I";
long lId = ptreeCZ.get<long>(sTagName));
sTagName = "CT";
long lCT = ptreeCZ.get<long>(sTagName));
// do something with id and ct...
// increment the count.
nCZCount++;
}
// nCZCount ends up set to 2 based on input XML above
// (this loop does NOT work)
sTagName = "CS";
const ptree proptreeCS = proptreeC.get_child(sTagName);
// (this does NOT work to iterate through <I> values under the <CS> node)
sTagName = "I";
BOOST_FOREACH(const ptree::value_type & vpairCS,
proptreeCS.get_child(sTagName))
{
// check to see if this is a "I" value; if not skip it.
if (vpairCS.first.data() != sTagName)
continue;
long lId = atol(vpairCS.second.data().c_str());
// do something with id...
}
// the above loop does NOT execute one time.
}
So how can I iterate through the "I" value peers under the "CS" node?
In the code in my question, I was asking for children too low in the tree. Here is the loop that will retrieve the "I" values from the "CS" node (replaces the last BOOST_FOREACH in the code in my question):
BOOST_FOREACH(const ptree::value_type & vpairI, proptreeC.get_child(std::str("CS")))
{
// check to see if this is an "I" value; if not skip it.
if (vpairCapability.first.data() != std::string("I"))
continue;
long lId = atol(vpairI.second.data().c_str());
// do something with lId...
}