Boost::Property_Tree Xml Pretty Printing

boost::property_tree XML pretty printing

The solution was to add the trim_whitespace flag to the call to read_xml:

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>

int main( void )
{
// Create an empty property tree object
using boost::property_tree::ptree;
ptree pt;

// reading file.xml
read_xml("file.xml", pt, boost::property_tree::xml_parser::trim_whitespace );

// writing the unchanged ptree in file2.xml
boost::property_tree::xml_writer_settings<char> settings('\t', 1);
write_xml("file2.xml", pt, std::locale(), settings);

return 0;
}

The flag is documented here but the current maintainer of the library (Sebastien Redl) was kind enough to answer and point me to it.

boost::property_tree xml pretty printing, formatting

The documentation of PropertyTree is pretty bad (I've recently started improving it). What you need to do is pass a correct xml_writer_settings object to write_xml.

https://github.com/boostorg/property_tree/blob/master/include/boost/property_tree/detail/xml_parser_writer_settings.hpp

write_xml(filename, tree, std::locale(),
xml_writer_make_settings(' ', 4));

Better XML formatting using Boost?

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>

Printing the xml generated by the boost property tree

Use following version of function

template<typename Ptree> 
void write_xml
(
std::basic_ostream< typename Ptree::key_type::value_type > & stream,
const Ptree & pt,
const xml_writer_settings< typename Ptree::key_type::value_type > & settings =
xml_writer_settings< typename Ptree::key_type::value_type >()
);

http://www.boost.org/doc/libs/1_52_0/doc/html/boost/property_tree/xml_parser/write_xml_id1233444.html

write_xml(std::cout, pt);

for output in console

std::ostringstream oss;
write_xml(oss, pt);

for output in stringstream (you can output stringstream contents in console, by using str function of stringstream).

http://liveworkspace.org/code/4qV9om$4

Boost property tree (XML) remove blank lines

Ok, it looks like the answer was already out there on Stack Overflow, I just hadn't found it (newlines were not mentioned in the post)

boost::property_tree XML pretty printing

The solution is to read the file with boost::property_tree::xml_parser::trim_whitespace

How to set target XML doctype using boost::property_tree::write_xml?

You can't. Boost Property Tree, unsurprisingly, is not an XML library. It's a property tree library.

To write XML, consider using an XML library: What XML parser should I use in C++?

Then again, there is potentially a hack using undocumented interface: removing encoding attribute from xml using boost

That way you effectively bypass the document writing code and you can substitute it with your own hack.

writing more complex than trivial xml with boost property tree

This answers the last question - how to use several nodes with the same name.
Finally I wrote such program that solves the problem

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>

//<Root>
// <Set Name="1">
// <Field Name="Hello 1"/>
// <Field Name="World 1"/>
// </Set>
// <Set Name="2">
// <Field Name="Hello 2"/>
// <Field Name="World 2"/>
// </Set>
//</Root>

int main(int argc, char* argv[])
{
using boost::property_tree::ptree;
ptree pt;

boost::property_tree::ptree rootNode;
boost::property_tree::ptree setNode1;
boost::property_tree::ptree setNode2;
boost::property_tree::ptree fieldNode1;
boost::property_tree::ptree fieldNode2;
boost::property_tree::ptree fieldNode3;
boost::property_tree::ptree fieldNode4;

fieldNode1.put("<xmlattr>.Name", "Hello 1");
fieldNode2.put("<xmlattr>.Name", "World 1");
fieldNode3.put("<xmlattr>.Name", "Hello 2");
fieldNode4.put("<xmlattr>.Name", "World 2");

setNode1.add_child("Field", fieldNode1);
setNode1.add_child("Field", fieldNode2);
setNode2.add_child("Field", fieldNode3);
setNode2.add_child("Field", fieldNode4);

setNode1.put("<xmlattr>.Name", "1");
setNode2.put("<xmlattr>.Name", "2");

rootNode.add_child("Set", setNode1);
rootNode.add_child("Set", setNode2);
pt.add_child("Root", rootNode);

boost::property_tree::xml_writer_settings<char> settings('\t', 1);
write_xml("testXml.xml", pt, std::locale(), settings);
return 0;
}

Output:

<?xml version="1.0" encoding="utf-8"?>
<Root>
<Set Name="1">
<Field Name="Hello 1"/>
<Field Name="World 1"/>
</Set>
<Set Name="2">
<Field Name="Hello 2"/>
<Field Name="World 2"/>
</Set>
</Root>

boost::property_tree : Parsing of Complex xml strucure

Ok, the usual anti-pattern when using Property Tree to parse information is "loop frenzy".

The whole idea of storing key-value pairs in a tree format is to avoid having to loop low-level structures, instead using convenient addressing (using paths).

Another anti-pattern is to have all the parsing in one big function. I'd split things up.

Define Some Data Types

Let's start with defining some data-types to keep our data manageable:

namespace Domain {
struct TOpts {
size_t count;
std::string format;
size_t timeout ;
};

struct TData {
std::string date; // YYMMD
std::string time; // HHMM
size_t ref;
};

struct TCustOpts {
std::multimap<std::string, std::string> params;
};

struct Txn {
std::string version;
TOpts opts;
TData data;
TCustOpts custom_opts;
};
}

This is our make-shift "Domain Layer".

Let's Parse!

So, here's how I'd write the parsing code:

namespace Parsing {
// concrete parse functions
void parse(Domain::TOpts& v, ptree const& pt) {
v.count = pt.get("<xmlattr>.tCount", 0);
v.format = pt.get("<xmlattr>.tformat", "0");
v.timeout = pt.get("<xmlattr>.ttimeout", 0);
}

void parse(Domain::TData& v, ptree const& pt) {
v.date = pt.get("Tvalue.<xmlattr>.date", "YYMMDD");
v.time = pt.get("Tvalue.<xmlattr>.time", "HHMM");
v.ref = pt.get("Tvalue.<xmlattr>.Ref", 0);
}

void parse(Domain::TCustOpts& v, ptree const& pt) {
for (auto& param : pt) {
if (param.first != "Param")
continue;

v.params.emplace(
param.second.get("<xmlattr>.name", "(anon)"),
param.second.get("<xmlattr>.value", ""));
}
}

// make any parse helper available optionally
template <typename T>
void parse_optional(T& v, boost::optional<ptree const&> pt) {
if (pt) parse(v, *pt);
}

void parse(Domain::Txn& v, ptree const& pt) {
v.version = pt.get("<xmlattr>.ver", "0.0");
parse_optional(v.opts, pt.get_child_optional("TOpts"));
parse_optional(v.data, pt.get_child_optional("TData"));
parse_optional(v.custom_opts, pt.get_child_optional("TCustOpts"));
}
}

The only not-so-straight-forward thing is parse_optional to deal with subtrees that might be absent.

Using it:

int main() {
boost::property_tree::ptree pt;
{
extern char const* xml;
std::stringstream ss(xml);
read_xml(ss, pt);
}

Domain::Txn transaction;
Parsing::parse(transaction, pt.get_child("Txn"));

std::cout << transaction; // complete roundtrip
}

BONUS: Roundtrip

Let's also save the same "Domain" classes back to a property tree, so we can verify it works:

namespace Writing { // for DEBUG/demo only
void serialize(Domain::TOpts const& v, ptree& pt) {
pt.put("<xmlattr>.tCount", v.count);
pt.put("<xmlattr>.tformat", v.format);
pt.put("<xmlattr>.ttimeout", v.timeout);
}

void serialize(Domain::TData const& v, ptree& pt) {
pt.put("Tvalue.<xmlattr>.date", v.date);
pt.put("Tvalue.<xmlattr>.time", v.time);
pt.put("Tvalue.<xmlattr>.Ref", v.ref);
}

void serialize(Domain::TCustOpts const& v, ptree& pt) {
for (auto& param : v.params) {
auto& p = pt.add_child("Param", ptree{});
p.put("<xmlattr>.name", param.first);
p.put("<xmlattr>.value", param.second);
}
}

void serialize(Domain::Txn const& v, ptree& pt) {
auto& txn = pt.add_child("Txn", ptree{});
txn.put("<xmlattr>.ver", v.version);
serialize(v.opts, txn.add_child("TOpts", ptree{}));
serialize(v.data, txn.add_child("TData", ptree{}));
serialize(v.custom_opts, txn.add_child("TCustOpts", ptree{}));
}
}

FULL DEMO

This demo shows your original XML parsed and serialized back:

Live On Coliru

#include <boost/property_tree/xml_parser.hpp>
#include <iostream>
#include <map>

using boost::property_tree::ptree;

namespace Domain {
struct TOpts {
size_t count;
std::string format;
size_t timeout ;
};

struct TData {
std::string date; // YYMMD
std::string time; // HHMM
size_t ref;
};

struct TCustOpts {
std::multimap<std::string, std::string> params;
};

struct Txn {
std::string version;
TOpts opts;
TData data;
TCustOpts custom_opts;
};
}

namespace Parsing {
// concrete parse functions
void parse(Domain::TOpts& v, ptree const& pt) {
v.count = pt.get("<xmlattr>.tCount", 0);
v.format = pt.get("<xmlattr>.tformat", "0");
v.timeout = pt.get("<xmlattr>.ttimeout", 0);
}

void parse(Domain::TData& v, ptree const& pt) {
v.date = pt.get("Tvalue.<xmlattr>.date", "YYMMDD");
v.time = pt.get("Tvalue.<xmlattr>.time", "HHMM");
v.ref = pt.get("Tvalue.<xmlattr>.Ref", 0);
}

void parse(Domain::TCustOpts& v, ptree const& pt) {
for (auto& param : pt) {
if (param.first != "Param")
continue;

v.params.emplace(
param.second.get("<xmlattr>.name", "(anon)"),
param.second.get("<xmlattr>.value", ""));
}
}

// make any parse helper available optionally
template <typename T>
void parse_optional(T& v, boost::optional<ptree const&> pt) {
if (pt) parse(v, *pt);
}

void parse(Domain::Txn& v, ptree const& pt) {
v.version = pt.get("<xmlattr>.ver", "0.0");
parse_optional(v.opts, pt.get_child_optional("TOpts"));
parse_optional(v.data, pt.get_child_optional("TData"));
parse_optional(v.custom_opts, pt.get_child_optional("TCustOpts"));
}
}

namespace Writing { // for DEBUG/demo only
void serialize(Domain::TOpts const& v, ptree& pt) {
pt.put("<xmlattr>.tCount", v.count);
pt.put("<xmlattr>.tformat", v.format);
pt.put("<xmlattr>.ttimeout", v.timeout);
}

void serialize(Domain::TData const& v, ptree& pt) {
pt.put("Tvalue.<xmlattr>.date", v.date);
pt.put("Tvalue.<xmlattr>.time", v.time);
pt.put("Tvalue.<xmlattr>.Ref", v.ref);
}

void serialize(Domain::TCustOpts const& v, ptree& pt) {
for (auto& param : v.params) {
auto& p = pt.add_child("Param", ptree{});
p.put("<xmlattr>.name", param.first);
p.put("<xmlattr>.value", param.second);
}
}

void serialize(Domain::Txn const& v, ptree& pt) {
auto& txn = pt.add_child("Txn", ptree{});
txn.put("<xmlattr>.ver", v.version);
serialize(v.opts, txn.add_child("TOpts", ptree{}));
serialize(v.data, txn.add_child("TData", ptree{}));
serialize(v.custom_opts, txn.add_child("TCustOpts", ptree{}));
}
}

namespace { // for debug/demo only
std::ostream& operator<<(std::ostream& os, Domain::Txn const& v) {
ptree tmp;
Writing::serialize(v, tmp);
write_xml(os, tmp, boost::property_tree::xml_writer_make_settings<std::string>(' ', 4));
return os;
}
}

int main() {
boost::property_tree::ptree pt;
{
extern char const* xml;
std::stringstream ss(xml);
read_xml(ss, pt);
}

Domain::Txn transaction;
Parsing::parse(transaction, pt.get_child("Txn"));

std::cout << transaction; // complete roundtrip
}

char const* xml = R"(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Txn ver="1.0">
<TOpts tCount="1" tformat="0" ttimeout="10" />
<TData>
<Tvalue date="YYMMDD" time="HHMM" Ref="100"/>
</TData>
<TCustOpts>
<Param name="SALE" value="xyz" />
</TCustOpts>
</Txn>
)";

Which prints:

<?xml version="1.0" encoding="utf-8"?>
<Txn ver="1.0">
<TOpts tCount="1" tformat="0" ttimeout="10"/>
<TData>
<Tvalue date="YYMMDD" time="HHMM"/>
</TData>
<TCustOpts>
<Param name="SALE" value="xyz"/>
</TCustOpts>
</Txn>


Related Topics



Leave a reply



Submit