I would like to know if there is a way to get the data in children nodes by iterating through a vector of the parent nodes. I have an XML file that I plan on changing frequently so hardcoding the attribute names is something I would like to avoid. Thus, I want to extract the data within my children nodes without stating the tag name of the nodes by using pt.get_child(myparentNodes)
. I have this in my main.
Any assistance is greatly appreciated!
vector<string> parentNodes;
ptree pt;
ifstream fileName("myxml");
read_xml(fileName, pt);
for(const ptree::value_type &parent : pt)
{
cout << parent.first << std::endl;
parentNodes.push_back(parent.first);
}
for(int i=0; i<parentNodes.size();i++)
{
BOOST_FOREACH(boost::property_tree::ptree::value_type const &node,pt.get_child(parentNodes[i]))
/* I'm having trouble properly accessing the children nodes here */
In your snippet (cleaned up a little):
std::vector<std::string> parentNodes;
for(auto const& parent : pt) {
std::cout << parent.first << std::endl;
parentNodes.push_back(parent.first);
}
appears to be collecting the names of tree nodes into parentNodes
. However, this assumes that the names be unique, or non-empty.
Property names need not be unique, nor are they guaranteed to be non-empty. In fact arrays in Property Tree are frequently modeled as nodes with only unnamed child nodes.
Then you had trouble getting the children of corresponding nodes. Let's first do it the tedious way (again, assuming the names are unique):
for (size_t i = 0; i < parentNodes.size(); i++) {
auto& parent = pt.get_child(parentNodes[i]);
for (auto const& child : parent) {
std::cout << child.first << std::endl;
}
}
Of course using ranged-for is much easier:
for (auto const& name : parentNodes) {
auto& parent = pt.get_child(name);
for (auto const& child : parent) {
std::cout << child.first << std::endl;
}
}
You can avoid the assumptions about naming as well as the second loop and the vector storage:
for (auto const& parent : pt) {
std::cout << parent.first << std::endl;
auto& node = parent.second;
for (auto const& child : node) {
std::cout << child.first << std::endl;
}
}
This is because the iterator points to a pair of (key, value)
. In fact, on a recent compiler you can write the loop with structured bindings:
for (auto const& [name, node] : pt) {
std::cout << name << std::endl;
for (auto const& child : node) {
std::cout << child.first << std::endl;
}
}
still doing the same.
You said you want to make this generic. However, assuming two-layer hierarchy of parent/child relations does not strike me as "generic". I linked you to some examples that show generic traversal (e.g. looking for patterns throughout the whole tree) last time, e.g. Iterating on xml file with boost - A function from that example:
#include <boost/property_tree/xml_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
static auto settings = boost::property_tree::xml_writer_make_settings<std::string>(' ', 4);
template <typename Out>
Out enumerate_nodes(ptree const& pt, ptree::path_type path, Out out) {
if (path.empty())
return out;
if (path.single()) {
auto name = path.reduce();
for (auto& child : pt) {
if (child.first == name)
*out++ = child.second;
}
} else {
auto head = path.reduce();
for (auto& child : pt) {
if (head == "*" || child.first == head) {
out = enumerate_nodes(child.second, path, out);
}
}
}
return out;
}
int main() {
std::ifstream fileName("input.xml");
ptree pt;
read_xml(fileName, pt);
for (auto const& [name, node] : pt) {
std::cout << name << std::endl;
for (auto const& child : node)
std::cout << child.first << std::endl;
}
std::vector<std::reference_wrapper<ptree const>> matched;
enumerate_nodes(pt, "root.parent2.child3", back_inserter(matched));
for (ptree const& match : matched)
std::cout << "Matched: " << match.get_value<std::string>() << "\n";
}
When using input.xml
:
<?xml version="1.0"?>
<root>
<parent1>
<child1>parent1/child1</child1>
<child2>parent1/child2</child2>
<child3>parent1/child3</child3>
<child4>parent1/child4</child4>
</parent1>
<parent2>
<child1>parent2/child1</child1>
<child2>parent2/child2</child2>
<child3>parent2/child3</child3>
<child4>parent2/child4</child4>
</parent2>
<parent3>
<child1>parent3/child1</child1>
<child2>parent3/child2</child2>
<child3>parent3/child3</child3>
<child4>parent3/child4</child4>
</parent3>
<parent4>
<child1>parent4/child1</child1>
<child2>parent4/child2</child2>
<child3>parent4/child3</child3>
<child4>parent4/child4</child4>
</parent4>
</root>
Prints
root
parent1
parent2
parent3
parent4
Matched: parent2/child3