Better XML formatting using Boost? [duplicate] - c++

This question already has answers here:
boost::property_tree XML pretty printing
(4 answers)
Closed 8 years ago.
I'm using Boost Property Trees to export my class-instants as XML nodes.
It works but it just puts everything in 1 line. I would like it to have indents, like:
<?xml version="1.0" encoding="utf-8"?>
<root>
<sensorconfigurations>
<configuration>
<name>SensorConfiguration1</name>
<sensorid>1</sensorid>
<signalindex>1</signalindex>
<mappingscheme>mappingscheme1</mappingscheme>
<soundpack>test1.wav</soundpack>
</configuration>
<configuration>
<name>SensorConfiguration2</name>
<sensorid>2</sensorid>
<signalindex>2</signalindex>
<mappingscheme>mappingscheme1</mappingscheme>
<soundpack>test2.wav</soundpack>
</configuration>
<configuration>
<name>SensorConfiguration3</name>
<sensorid>3</sensorid>
<signalindex>3</signalindex>
<mappingscheme>mappingscheme2</mappingscheme>
<soundpack>test3.wav</soundpack>
</configuration>
</sensorconfigurations>
</root>
Is this possible somehow? Am I missing a parameter in the write_xml method?
Here's my code:
void SensorConfigurationBank::save()
{
using boost::property_tree::ptree;
ptree pt;
for(map<string, SensorConfiguration>:: iterator it = sensorConfigurations_.begin(); it != sensorConfigurations_.end(); ++it)
{
ptree myTree;
myTree.put("name", it->second.getName());
myTree.put("sensorid", it->second.getSensorID());
myTree.put("signalindex", it->second.getsignalIndex());
MappingScheme myScheme = it->second.getMScheme();
myTree.put("mappingscheme", myScheme.getId());
SoundPack mySound = it->second.getSound();
myTree.put("soundpack", mySound.filepath_);
pt.add_child("root.sensorconfigurations.configuration", myTree);
}
write_xml("SensorConfigurationBank2.xml", pt);
}

These days, xml_writer_settings apparently takes a string type as template argument, so:
boost::property_tree::xml_writer_settings<std::string> settings('\t', 1);
write_xml(std::cout, pt, settings);
will do the trick. Full sample:
Live On Coliru
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <map>
#include <iostream>
struct SoundPack {
std::string filepath_ = "soundpack.wav";
};
struct MappingScheme {
std::string getId() const { return "Id"; }
};
struct SensorConfiguration {
std::string getName() const { return "Name"; }
std::string getSensorID() const { return "SensorID"; }
std::string getsignalIndex() const { return "signalIndex"; }
SoundPack getSound() const { return {}; }
MappingScheme getMScheme() const { return {}; }
};
void save(std::map<std::string, SensorConfiguration> sensorConfigurations_)
{
using boost::property_tree::ptree;
ptree pt;
for(std::map<std::string, SensorConfiguration>:: iterator it = sensorConfigurations_.begin(); it != sensorConfigurations_.end(); ++it)
{
ptree myTree;
MappingScheme myScheme = it->second.getMScheme();
SoundPack mySound = it->second.getSound();
myTree.put("name", it->second.getName());
myTree.put("sensorid", it->second.getSensorID());
myTree.put("signalindex", it->second.getsignalIndex());
myTree.put("mappingscheme", myScheme.getId());
myTree.put("soundpack", mySound.filepath_);
pt.add_child("root.sensorconfigurations.configuration", myTree);
}
boost::property_tree::xml_writer_settings<std::string> settings('\t', 1);
write_xml(std::cout, pt, settings);
}
int main() {
save({
{ "first", SensorConfiguration {} },
{ "second", SensorConfiguration {} },
{ "third", SensorConfiguration {} },
{ "fourth", SensorConfiguration {} }
});
}
Output:
<?xml version="1.0" encoding="utf-8"?>
<root>
<sensorconfigurations>
<configuration>
<name>Name</name>
<sensorid>SensorID</sensorid>
<signalindex>signalIndex</signalindex>
<mappingscheme>Id</mappingscheme>
<soundpack>soundpack.wav</soundpack>
</configuration>
<configuration>
<name>Name</name>
<sensorid>SensorID</sensorid>
<signalindex>signalIndex</signalindex>
<mappingscheme>Id</mappingscheme>
<soundpack>soundpack.wav</soundpack>
</configuration>
<configuration>
<name>Name</name>
<sensorid>SensorID</sensorid>
<signalindex>signalIndex</signalindex>
<mappingscheme>Id</mappingscheme>
<soundpack>soundpack.wav</soundpack>
</configuration>
<configuration>
<name>Name</name>
<sensorid>SensorID</sensorid>
<signalindex>signalIndex</signalindex>
<mappingscheme>Id</mappingscheme>
<soundpack>soundpack.wav</soundpack>
</configuration>
</sensorconfigurations>
</root>

Related

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 erase child from boost tree if it is double registered?

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.

How to read through nodes using pugixml?

I have just downloaded the pugixml library and I am trying to adapt it to my needs. It is mostly oriented for DOM style which I am not using. The data I store looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<profile>
<points>
<point>
<index>0</index>
<x>0</x>
<y>50</y>
</point>
<point>
<index>1</index>
<x>2</x>
<y>49.9583</y>
</point>
<point>
<index>2</index>
<x>12</x>
<y>50.3083</y>
</point>
</points>
</profile>
Pugixml guide says:
It is common to store data as text contents of some node - i.e.
This is a node. In this case,
node does not have a value, but instead has a child of
type node_pcdata with value "This is a node". pugixml provides
child_value() and text() helper functions to parse such data.
But I am having problem with using those methods, I am not getting the node values out.
#include "pugixml.hpp"
#include <string.h>
#include <iostream>
int main()
{
pugi::xml_document doc;
if (!doc.load_file("/home/lukasz/Programy/eclipse_linux_projects/xmlTest/Debug/pidtest.xml"))
return -1;
pugi::xml_node points = doc.child("profile").child("points");
for (pugi::xml_node point = points.first_child(); point; point = points.next_sibling())
{
// ?
}
return 0;
}
How to read out the index, x and y values inside of the for? I Would aprichiate all help.
There are several ways, documented in the quickstart page:
http://pugixml.org/docs/samples/traverse_iter.cpp
http://pugixml.org/docs/samples/traverse_rangefor.cpp
there is a tree visitor for the power jobs http://pugixml.org/docs/samples/traverse_walker.cpp
May I suggest Xpath?
#include <pugixml.hpp>
#include <iostream>
int main()
{
pugi::xml_document doc;
if (doc.load_file("input.txt")) {
for (auto point : doc.select_nodes("//profile/points/point")) {
point.node().print(std::cout, "", pugi::format_raw);
std::cout << "\n";
}
}
}
Prints
<point><index>0</index><x>0</x><y>50</y></point>
<point><index>1</index><x>2</x><y>49.9583</y></point>
<point><index>2</index><x>12</x><y>50.3083</y></point>

Need help recursively creating a directory tree using the Boost Property Tree library and XML

I've created an XML file that represents a directory layout for a project. It looks like this:
<folder>
<folder>
<name>src</name>
<file>
<name>main.cpp</name>
</file>
</folder>
<file>
<name>Makefile</name>
</file>
<file>
<name>README.md</name>
</file>
</folder>
I'm using the Boost property tree (boost::property_tree::ptree) to parse, represent, and create the directory (the program I'm trying to write is a command line tool that generates empty C++ projects). I'm trying to write a function that will create the directory recursively, but I've never used this library before, am currently running into a few mental blocks, and feel like I'm going about it all wrong. If anyone has used this library before and can give me a few pointers with my code, I'd appreciate it. Here's what I have so far:
static void create_directory_tree(std::string &root_name,
boost::property_tree::ptree &directory_tree)
{
// Create the root folder.
boost::filesystem::create_directory(root_name);
for (auto &tree_value : directory_tree.get_child("folder"))
{
// If the child is a file, create an empty file with the
// name attribute.
if (tree_value.first == "file")
{
std::ofstream file(tree_value.second.get<std::string>("name"));
file << std::flush;
file.close();
}
// If the child is a folder, call this function again with
// the folder as the root. I don't understand the data
// structure enough to know how to access this for my
// second parameter.
else if (tree_value.first == "folder")
{
create_directory_tree(tree_value.second.get<std::string>("name"), /* ??? */)
}
// If it's not a file or folder, something's wrong with the XML file.
else
{
throw std::runtime_error("");
}
}
}
It's not exactly clear to me what you're asking.
I hope my take on it helps:
Live On Coliru
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/filesystem.hpp>
#include <iostream>
using namespace boost::property_tree;
namespace fs = boost::filesystem;
namespace project_definition {
void apply(ptree const& def, fs::path const& rootFolder) {
for (auto node : def) {
if ("folder" == node.first) {
fs::path where = rootFolder / node.second.get<std::string>("name");
fs::create_directories(where);
apply(node.second, where);
}
if ("file" == node.first) {
std::ofstream((rootFolder / node.second.get<std::string>("name")).native(), std::ios::trunc);
}
}
}
}
int main()
{
ptree projdef;
read_xml("input.txt", projdef);
try {
project_definition::apply(projdef, "./rootfolder/");
} catch(std::exception const& e)
{
std::cerr << e.what() << "\n";
}
}
With a input.txt of
<folder>
<name>project</name>
<folder>
<name>src</name>
<file><name>main.cpp</name></file>
</folder>
<file><name>Makefile</name></file>
<file><name>README.md</name></file>
</folder>
Creates a structure:
./rootfolder
./rootfolder/project
./rootfolder/project/README.md
./rootfolder/project/src
./rootfolder/project/src/main.cpp
./rootfolder/project/Makefile