How to erase child from boost tree if it is double registered? - c++

Could you please help me with the following?
I populate this finalTree = treeA + treeB
However, the problem is that some elements of treeB have the same name with some of treeA. As a result I might have double registries for some children.
ie.
<category>
<fruit type="banana">
<characteristic>
<point v="0"/>
</characteristic>
</fruit>
<fruit type="orange">
<characteristic>
<point v="1"/>
</characteristic>
</fruit>
<fruit type="banana">
<characteristic>
<point v="2"/>
</characteristic>
</fruit>
<fruit type="fig">
<characteristic>
<point v="3"/>
</characteristic>
</fruit>
</category>
What I want to achieve is to delete the first entry of banana and keep the last.
So far I do:
boost::property_tree::ptree & node = informationTree.add("information.fruitTypes", "");
node.add("<xmlattr>.type", fruit);
node.add_child("characteristic", char);
The problem is that I don't know how to remove it, as I don't know whether the double entry will be banana or something else the next time. Should I copy populate tree? What do you suggest please?

If you're just building the tree, you can just use put_* instead of add_* and it would overwrite an element if it already exists by that name.
If you have a tree and want to remove the duplicates at a certain subtree, you have to do it manually, e.g.:
Live On Coliru
#include <boost/property_tree/xml_parser.hpp>
#include <iostream>
#include <map>
using boost::property_tree::ptree;
template <typename KeyF>
ptree nodup(ptree const& pt, KeyF key_accessor) {
ptree filtered;
std::map<std::string, std::reference_wrapper<ptree> > seen;
for (auto& entry : pt) {
auto key = key_accessor(entry);
auto previous = seen.find(key);
if (seen.end() == previous)
seen.emplace(key, filtered.add_child(entry.first, entry.second));
else
previous->second.get() = entry.second; // overwrite
}
return filtered;
}
int main() {
ptree pt;
{
std::istringstream iss( "<category><fruit type=\"banana\"><characteristic><point v=\"0\"/></characteristic></fruit><fruit type=\"orange\"><characteristic><point v=\"1\"/></characteristic></fruit><fruit type=\"banana\"><characteristic><point v=\"2\"/></characteristic></fruit><fruit type=\"fig\"><characteristic><point v=\"3\"/></characteristic></fruit></category>");
read_xml(iss, pt);
}
write_xml(std::cout, pt, boost::property_tree::xml_writer_make_settings<std::string>(' ', 4, "utf-8"));
auto& subtree = pt.get_child("category");
subtree = nodup(subtree, [](ptree::value_type const& item) { return item.second.get("<xmlattr>.type", ""); });
write_xml(std::cout, pt, boost::property_tree::xml_writer_make_settings<std::string>(' ', 4, "utf-8"));
}
If you are a bit more performance concerned, you can iterate backwards and avoid some overwrite actions:
Live On Coliru
template <typename KeyF>
ptree nodup(ptree const& pt, KeyF key_accessor) {
ptree filtered;
std::map<std::string, std::reference_wrapper<ptree> > seen;
for (auto entry = pt.rbegin(), last = pt.rend(); entry != last; ++entry) {
auto key = key_accessor(*entry);
auto previous = seen.find(key);
if (seen.end() == previous)
seen.emplace(key, filtered.add_child(entry->first, entry->second));
}
return filtered;
}
However, keep in mind this potentially alters the order in which the fruits appear in the subtree.

Related

recursively get complete key path to all values in a boost property tree

I'm reading an XML file into a boost::property_tree and trying to get the complete key path for every value.
Does boost have a built in way to do this
Were is the error in my recursion?
example input - my_file.xml
<foo>
<bar>abc</bar>
<baz>
<buz>def</buz>
</baz>
</foo>
desired result
"foo.bar"
"foo.baz.buz"
actual result (wrong)
"foo.bar"
"foo.bar.baz.buz"
code that doesn't quite work
void walk_ptree(boost::property_tree::ptree tree, std::unordered_set<std::string>& key_set, std::string parent_key)
{
if (tree.empty())
{
key_set.insert(parent_key);
return;
}
for (auto& it : tree)
{
// add a separator between key segments
if (!parent_key.empty())
{
parent_key += ".";
}
parent_key += it.first;
walk_ptree(it.second, key_set, parent_key);
}
}
boost::property_tree::ptree prop_tree;
boost::property_tree::read_xml("my_file.xml", prop_tree);
std::unordered_set<std::string> key_set{};
walk_ptree(prop_tree, key_set, "");
Every iteration of your for loop adds to the parentKey value so it will have all the children's names in it by the end of the loop. Use a separate variable to hold the key name for each node:
void walk_ptree(const boost::property_tree::ptree& tree, std::unordered_set<std::string>& key_set, const std::string& parent_key)
{
if (tree.empty())
{
key_set.insert(parent_key);
return;
}
for (auto& it : tree)
{
std::string key = parent_key;
// add a separator between key segments
if (!key.empty())
{
key += ".";
}
key+= it.first;
walk_ptree(it.second, key_set, key);
}
}

Issue in updating XML attribute value for every node with the same name using boost library

I am trying to update the value of totalresult attribute in every test_list node found. The issue is, it will only update the first test_list node found.
The testListCount will increment every time a test_list node is added. Once done adding test_list node, each totalresult value will then be updated in every test_list node.
Here is my code:
BOOST_FOREACH(ptree::value_type const & subTree, mainTree.get_child("my_report"))
{
auto &nodeTestList = mainTree.get_child("my_report.test_list");
BOOST_FOREACH(ptree::value_type const & subval, nodeTestList)
{
ptree subvalTree = subval.second;
BOOST_FOREACH(ptree::value_type const & paramNode, subvalTree)
{
std::string name = paramNode.first;
if (name == TestListAttrib[TestListParam::TOTALRESULT])
{
wxMessageBox("firing!");
nodeTestList.put("<xmlattr>." + name, testListCount);
}
}
}
}
Below is the actual result:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="report.xsl"?>
<my_report>
<test_list overall_status="FAILED" result="1" totalresult="3">
<test_list overall_status="FAILED" result="2" totalresult=""/>
<test_list overall_status="FAILED" result="3" totalresult=""/>
</my_report>
Below is the expected result:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="report.xsl"?>
<my_report>
<test_list overall_status="FAILED" result="1" totalresult="3">
<test_list overall_status="FAILED" result="2" totalresult="3"/>
<test_list overall_status="FAILED" result="3" totalresult="3"/>
</my_report>
Like others said, Property Tree is not an XML library (see What XML parser should I use in C++?).
That said, it looks like your error is here:
for (auto const &subTree : mainTree.get_child("my_report")) {
auto &nodeTestList = mainTree.get_child("my_report.test_list");
The second line doesn't use subTree at all, instead it just matches the first "my_report.test_list" node from mainTree.
Use Modern C++ And Compiler Warnings
I made the code self-contained c++11:
#include <boost/property_tree/xml_parser.hpp>
using boost::property_tree::ptree;
enum TestListParam { OVERALLSTATUS, TOTALRESULT };
std::array<std::string, 2> TestListAttrib{ "overall_status", "totalresult" };
int main() {
ptree mainTree;
{
std::ifstream ifs("input.xml");
read_xml(ifs, mainTree);
}
auto const testListCount = 3;
for (auto const& subTree : mainTree.get_child("my_report")) {
auto& nodeTestList = mainTree.get_child("my_report.test_list");
for (auto& subval : nodeTestList) {
ptree subvalTree = subval.second;
for (auto& paramNode : subvalTree) {
std::string name = paramNode.first;
if (name == TestListAttrib[TestListParam::TOTALRESULT]) {
nodeTestList.put("<xmlattr>." + name, testListCount);
}
}
}
}
}
If you enable compiler warnings, you will see your error:
Live On Wandbox
prog.cc:16:22: warning: unused variable 'subTree' [-Wunused-variable]
for (auto const& subTree : mainTree.get_child("my_report")) {
^
1 warning generated.
More Modern C++
Using the niceties of C++17 things become cleaner and easier fixed. Here's a first shot, also adding output printing:
Live On Wandbox
#include <boost/property_tree/xml_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
auto const pretty = boost::property_tree::xml_writer_make_settings<std::string>(' ', 4);
enum TestListParam { OVERALLSTATUS, TOTALRESULT };
std::array<std::string, 2> TestListAttrib{ "overall_status", "totalresult" };
int main() {
ptree mainTree;
{
std::ifstream ifs("input.xml");
read_xml(ifs, mainTree);
}
auto const testListCount = 3;
for (auto& [key, subTree] : mainTree.get_child("my_report"))
for (auto& [name, node] : subTree.get_child("<xmlattr>")) {
if (name == TestListAttrib[TestListParam::TOTALRESULT]) {
node.put_value(testListCount);
}
}
write_xml(std::cout, mainTree, pretty);
}
Prints: (whitespace reduced)
<?xml version="1.0" encoding="utf-8"?>
<my_report>
<test_list overall_status="FAILED" result="1" totalresult="3"/>
<test_list overall_status="FAILED" result="2" totalresult="3"/>
<test_list overall_status="FAILED" result="3" totalresult="3"/>
</my_report>
Caveats
Note how because of the way we write the loops the code
will fail if <xmlattr> or my_report are not found
Conversely, it will erroneously descend all child nodes of my_report even if they have different names than test_list
the XSL processing instruction is lost. Once again, this is inherent because Boost Property Tree doesn't know about XML. It uses a subset of XML to implement serialization for property trees.
To fix the first two bullets, I'd suggest making a helper to query nodes from your XML (from Iterating on xml file with boost):
enumerate_nodes(mainTree,
"my_report.test_list.<xmlattr>.totalresult",
back_inserter(nodes));
This doesn't suffer from any of the problems mentioned, and you can elegantly assing all matching nodes:
for (ptree& node : nodes)
node.put_value(3);
If you really didn't /want/ to require the test_list node name, use a wildcard:
enumerate_nodes(mainTree,
"my_report.*.<xmlattr>.totalresult",
back_inserter(nodes));
Live Demo
Live On Wandbox
#include <boost/property_tree/xml_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
auto const pretty = boost::property_tree::xml_writer_make_settings<std::string>(' ', 4);
enum TestListParam { OVERALLSTATUS, TOTALRESULT };
std::array<std::string, 2> TestListAttrib{ "overall_status", "totalresult" };
template <typename Ptree, typename Out>
Out enumerate_nodes(Ptree& 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() {
ptree mainTree;
{
std::ifstream ifs("input.xml");
read_xml(ifs, mainTree);
}
std::vector<std::reference_wrapper<ptree> > nodes;
enumerate_nodes(mainTree,
"my_report.test_list.<xmlattr>.totalresult",
back_inserter(nodes));
for (ptree& node : nodes)
node.put_value(3);
write_xml(std::cout, mainTree, pretty);
}
Prints
<?xml version="1.0" encoding="utf-8"?>
<my_report>
<test_list overall_status="FAILED" result="1" totalresult="3"/>
<test_list overall_status="FAILED" result="2" totalresult="3"/>
<test_list overall_status="FAILED" result="3" totalresult="3"/>
</my_report>

Boost and xml parsing

I have following xml data and i want to parse through boost xml parser.
<?xml version="1.0" encoding="UTF-8"?>
<applications>
<application>
<id>1</id>
<platform>linux-x64</platform>
<version>2.4</version>
</application>
<application>
<id>2</id>
<platform>windows</platform>
<version>2.5</version>
</application>
<application>
<id>3</id>
<platform>linux</platform>
<version>2.6</version>
</application>
</applications>
I have written below boost code but I read only first child of "applications" and not able to read other two childs. Everytime inner loop get the data of first child.
boost::property_tree::ptree pt;
boost::property_tree::read_xml(sModel, pt); // sModel is filename which contains above xml data
BOOST_FOREACH(boost::property_tree::ptree::value_type &v, pt.get_child("applications"))
{
std::string key = v.first.data();
std::string Id, platform, version;
if (key == std::string("application"))
{
BOOST_FOREACH(boost::property_tree::ptree::value_type &v_, pt.get_child("applications.application"))
{
std::string app_key = v_.first.data();
std::string app_value = v_.second.data();
if (app_key == std::string("id"))
pkgId = app_value;
else if (app_key == std::string("platform"))
platform = app_value;
else if (app_key == std::string("version"))
version = app_value;
}
}
}
Here, every time i get the platform as "linux-x64".
Can someone guide how to read all the child through this boost xml ?
Thanks in Advance.
get_child (and all the other path-based access functions) isn't very good at dealing with multiple identical keys. It will choose the first child with the given key and return that, ignoring all others.
But you don't need get_child, because you already hold the node you want in your hand.
pt.get_child("applications") gives you a ptree. Iterating over that gives you a ptree::value_type, which is a std::pair<std::string, ptree>.
The first weird thing, then, is this line:
std::string key = v.first.data();
The data() function you're calling here is std::string::data, not ptree::data. You could just write
std::string key = v.first;
The next strange thing is the comparison:
if (key == std::string("application"))
You don't need to explicitly construct a std::string here. In fact, doing so is a pessimization, because it has to allocate a string buffer and copy the string there, when std::string has comparison operators for C-style strings.
Then you iterator over pt.get_child("applications.application"), but you don't need to do this lookup - v.second is already the tree you want.
Furthermore, you don't need to iterate over the child at all, you can use its lookup functions to get what you need.
std::string pkgId = v.second.get("id", "");
So to sum up, this is the code I would write:
boost::property_tree::ptree pt;
boost::property_tree::read_xml(sModel, pt);
BOOST_FOREACH(boost::property_tree::ptree::value_type &v, pt.get_child("applications"))
{
// You can even omit this check if you can rely on all children
// being application nodes.
if (v.first == "application")
{
std::string pkgId = v.second.get("id", "");
std::string platform = v.second.get("platform", "");
std::string version = v.second.get("version", "");
}
}
Check this example:
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/foreach.hpp>
struct Application
{
int m_id
std::string m_platform;
float m_version;
};
typedef std::vector<Application> AppList;
AppList Read()
{
using boost::property_tree::ptree;
// Populate tree structure (pt):
ptree pt;
read_xml("applications.xml", pt); // For example.
// Traverse pt:
AppList List;
BOOST_FOREACH(ptree::value_type const& v, pt.get_child("applications"))
{
if (v.first == "application")
{
Application App;
App.id = v.second.get<int>("id");
App.platform = v.second.get<std::string>("platform");
App.version = v.second.get<float>("version");
List.push_back(App);
}
}
return List;
}

How to use boost c++ property map to extract values from config file

I have a config file like this, where properties1 can have 1 or more values:
[OBJECT]
properties1=val1, val2, val3
porperty2=Blah
property3=Blah
property4=Blah
porperty5=Bla bla bla
[OBJECT]
properties1=val1
porperty2=Blah
property3=Blah
property4=Blah
porperty5=Bla bla bla
And I am parsing it using boost::program_options. However it does not work properly because for example properties1 gets stored into one vector, and so it is not possible to get a mapping between each OBJECT and its properties. So I think boost::property_map might be a good solution. Here is the current solution:
namespace po = boost::program_options;
std::vector<std::string> properties1_all;
std::vector<std::string> property2_all;
std::vector<std::string> property3_all;
std::vector<std::string> property4_all;
std::vector<std::string> property5_all;
po::options_description desc;
desc.add_options()
("OBJECT.properties1",
po::value<std::vector<std::string> >(&properties1_all))
("OBJECT.property2",
po::value<std::vector<std::string> >(&property2_all))
("OBJECT.property3",
po::value<std::vector<std::string> >(&property3_all))
("OBJECT.property4",
po::value<std::vector<std::string> >(&property4_all))
("OBJECT.property5",
po::value<std::vector<std::string> >(&property5_all));
po::variables_map vm;
std::ifstream settings_file("config.ini", std::ifstream::in);
po::store(po::parse_config_file(settings_file, desc), vm);
settings_file.close();
po::notify(vm);
And my attempt with property map, but it does not seem to work... I think making each OBJECT a node and then an edge to 5 vertices, which are its properties, would be a good solution:
typedef adjacency_list<vecS, vecS, bidirectionalS,
no_property, property<edge_index_t, std::size_t> > Graph;
const int num_vertices = 5;
Graph G(num_vertices);
int propertyarray[] = { 1, 2, 3, 4, 5 };
int objectarray[] = { 1, 2, 3 };
// Add edges to the graph, and assign each edge an ID number.
add_edge(0, 1, 0, G);
// ...
typedef graph_traits<Graph>::edge_descriptor Edge;
typedef property_map<Graph, edge_index_t>::type EdgeID_Map;
EdgeID_Map edge_id = get(edge_index, G);
You've got the library wrong. Property Map is a generic "lense"-like library for accessing (read/write) properties based on some kind of input parameter.
To add insult to injury the code sample is (obviously?) from Boost Graph, the mother library that Boost Propery Map was born from.
Now, you wanted Boost Property Tree. The samples are pretty easy to find, and here's one more:
Live On Coliru
#include <iostream>
#include <map>
#include <boost/property_tree/ini_parser.hpp>
static std::string const sample = R"(
[BLA]
properties1=val1, val2, val3
property2=Blah
property3=Blah
property4=Blah
property5=Bla bla bla
[BLO]
properties1=val1
property2=Blah
property3=Blah
property4=Blah
property5=Bla bla bla
)";
struct Object {
std::string properties1, property2, property3, property4, property5;
};
using boost::property_tree::ptree;
void read(ptree const& pt, std::string& s) {
s = pt.get_value(s);
}
void read(ptree const& pt, Object& object) {
read(pt.get_child("properties1"), object.properties1);
read(pt.get_child("property2"), object.property2);
read(pt.get_child("property3"), object.property3);
read(pt.get_child("property4"), object.property4);
read(pt.get_child("property5"), object.property5);
}
template <typename T>
T read_as(ptree const& pt) {
T v;
read(pt, v);
return v;
}
int main() {
std::istringstream iss(sample);
boost::property_tree::ptree config;
read_ini(iss, config);
std::map<std::string, Object> parsed;
for (auto& section : config) {
parsed[section.first] = read_as<Object>(section.second);
}
for (auto& object : parsed) {
std::cout << "Parsed object named " << object.first << " (e.g. property5 is '" << object.second.property5 << "')\n";
}
}
Printing:
Parsed object named BLA (e.g. property5 is 'Bla bla bla')
Parsed object named BLO (e.g. property5 is 'Bla bla bla')

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...
}