In response to the older question I had this:
A few approaches
Opaque Member Object
One way would be to treat the student
property just like the Name
property:
dp.property("Name", boost::get(&GraphData::Name, graph));
dp.property("Student", boost::get(&GraphData::student, graph));
All that's required is you tell the standard library how to stream
Student
objects:
inline static std::ostream& operator<<(std::ostream& os, Student const& s) {
return os << s.roll_no << " " << std::quoted(s.division);
}
inline static std::istream& operator>>(std::istream& is, Student& s) {
return is >> s.roll_no >> std::ws >> std::quoted(s.division);
}
You'll get XML Like Live On
Wandbox.
<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
Mary-Anne Hornam
80 "alchemy"
Mary-Anne Bufgloon
57 "drama"
Joyce Preet
8 "drama"
Philomena Joyce
3 "drama"
James Tarsinck
78 "science"
Separate Properties
If you really want the separate GraphML properties roll_no
and
division
, then you go back to your transform_value_property_map
:
void WriteGraph(std::ostream &os, Graph &graph) {
boost::dynamic_properties dp;
auto roll_no = [](Student const& s) { return s.roll_no; };
auto division = [](Student const& s) { return s.division; };
dp.property("Name", get(&GraphData::Name, graph));
dp.property("roll_no", boost::make_transform_value_property_map(roll_no,
get(&GraphData::student, graph)));
dp.property("division", boost::make_transform_value_property_map(division,
get(&GraphData::student, graph)));
boost::write_graphml(os, graph, dp, true);
}
Note that for reading purposes, a LvaluePropertyMap is required, so it
needs to be written slightly more permissive:
Graph ReadGraph(std::string const &fileName) {
Graph graph;
boost::dynamic_properties dp;
auto roll_no = [](Student& s) ->auto& { return s.roll_no; };
auto division = [](Student& s) ->auto& { return s.division; };
dp.property("Name", get(&GraphData::Name, graph));
dp.property("roll_no", boost::make_transform_value_property_map(roll_no,
get(&GraphData::student, graph)));
dp.property("division", boost::make_transform_value_property_map(division,
get(&GraphData::student, graph)));
std::ifstream file(fileName);
boost::read_graphml(file, graph, dp);
return graph;
}
Now, you'll get XML Like Live On
Wandbox.
<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
Philomena Glinka
science
3
Philomena Preet
alchemy
84
John Habakuk
alchemy
19
Ernest Habakuk
philosophy
31
John Bufgloon
science
44
Simplifying
You can do without the lambdas and the make the differences between
write/read go away, by using std::mem_fn
:
static inline boost::dynamic_properties DynProps(Graph& g) {
boost::dynamic_properties dp;
dp.property("Name", get(&GraphData::Name, g));
auto student = get(&GraphData::student, g);
dp.property("roll_no", make_transform_value_property_map(std::mem_fn(&Student::roll_no),
student));
dp.property("division", make_transform_value_property_map(std::mem_fn(&Student::division),
student));
return dp;
}
Which can be used for ReadGraph
and WriteGraph
as follows:
Graph ReadGraph(std::string const &fileName) {
Graph graph;
auto dp = DynProps(graph);
std::ifstream file(fileName);
boost::read_graphml(file, graph, dp);
return graph;
}
void WriteGraph(std::ostream &os, Graph &graph) {
boost::write_graphml(os, graph, DynProps(graph), true);
}
You still get the same XML.
FULL LISTING
Live On Wandbox
#include <boost/graph/adjacency_list.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <boost/graph/graphml.hpp>
struct Student {
int roll_no;
std::string division;
};
struct GraphData {
std::string Name;
Student student;
};
using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::directedS, GraphData>;
static inline boost::dynamic_properties DynProps(Graph& g) {
boost::dynamic_properties dp;
dp.property("Name", get(&GraphData::Name, g));
auto student = get(&GraphData::student, g);
dp.property("roll_no", make_transform_value_property_map(std::mem_fn(&Student::roll_no),
student));
dp.property("division", make_transform_value_property_map(std::mem_fn(&Student::division),
student));
return dp;
}
Graph ReadGraph(std::string const &fileName) {
Graph graph;
auto dp = DynProps(graph);
std::ifstream file(fileName);
boost::read_graphml(file, graph, dp);
return graph;
}
void WriteGraph(std::ostream &os, Graph &graph) {
boost::write_graphml(os, graph, DynProps(graph), true);
}
void WriteGraph(std::string const& fileName, Graph &graph) {
std::ofstream ofs(fileName);
WriteGraph(ofs, graph);
}
#include <boost/graph/graph_utility.hpp>
namespace Gen { Graph graph(); } // generate random data
int main() {
{
Graph g = Gen::graph();
WriteGraph("input.txt", g);
}
Graph g = ReadGraph("input.txt");
print_graph(g, get(&GraphData::Name, g));
// or as XML
WriteGraph(std::cout << "==== XML version: ====\n\n", g);
}
/// generate data
#include <boost/graph/random.hpp>
#include <boost/random.hpp>
#include <random>
namespace Gen {
namespace {
namespace R = boost::random;
R::mt19937 engine {42}; // { std::random_device{}() };
template <typename Range> auto sample(Range const &from) {
return *std::next(boost::begin(from), R::uniform_int_distribution<>(1, boost::size(from))(engine) - 1);
}
int roll() { return R::uniform_int_distribution<>(1, 100)(engine); }
std::string division() {
static std::string const d[] = { "science", "drama", "mathematics", "philosophy", "alchemy" };
return sample(d);
}
std::string name() {
static std::string const f[] = { "John", "Daisy", "Chuck", "Mary-Anne", "Ernest", "Philomena", "Joyce", "James" };
static std::string const l[] = { "Joyce", "Habakuk", "Hornam", "Bufgloon", "Glinka", "Tarsinck", "Preet" };
return sample(f) + " " + sample(l);
}
Student student() { return { roll(), division() }; }
}
Graph graph() {
Graph g;
boost::generate_random_graph(g, 5, 7, engine);
for (auto vd: boost::make_iterator_range(vertices(g)))
g[vd] = { name(), student() };
return g;
}
} // namespace Gen
When You Have A Vector Member
Your current question adds vector
to the mix. There's a related post here: read boost graph (boost::read_graphviz) where vertex contains vector
You likely ran into the ADL trap with operator<<
/operator>>
for std::vector<T>
.
In case the above doesn't already solve the issue you were having, I'll add a demo later tonight.
Demonstration
This was trickier than I'd have hoped, because overloading operator<<
/operator>>
inside namespace ::std
is just poor taste, so we need a wrapper type:
using StudentInfo = std::vector<std::pair<std::string, int>>;
struct Wrapper {
StudentInfo& _si;
friend std::ostream& operator<<(std::ostream& os, const Wrapper sis) {
for(auto& pair : sis._si)
os << std::quoted(pair.first) << pair.second << ';';
return os;
}
friend std::istream& operator>>(std::istream& is, const Wrapper sis) {
StudentInfo::value_type pair;
while (is >> std::quoted(pair.first)) {
char ch;
if (is >> pair.second >> ch && ch == ';')
sis._si.push_back(pair);
else
return is;
}
if (!is.bad()) // eof this point is ok
is.clear();
return is;
}
};
Note, below code adds ", "
between name and age.
Now, some extra obstacles appear, since e.g. TransformValuePropertyMap is not an LValuePropertyMap unless the returned value is a mutable reference.
I opted to do a simple CoercePropertyMap to "wrap" a property with a a wrapping type:
template <typename T, typename Map> struct CoercePropertyMap : Map {
CoercePropertyMap(Map map) : Map(map){}
using value_type = T;
using reference = T;
};
template <typename T, typename Map>
CoercePropertyMap<T, Map> coerce_map(Map map) { return map; }
Now, we can put it all together:
dp.property("studentInfo", coerce_map(get(&GraphItem::studentInfo, g)));
Full Listing
Live On Wandbox
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphml.hpp>
#include <iostream>
#include <iomanip>
using StudentInfo = std::vector<std::pair<std::string, int>>;
namespace /*static*/ {
struct Lit { char ch; };
static inline std::istream& operator>>(std::istream& is, Lit expected) {
char actual;
if (is >> actual && actual != expected.ch)
is.setstate(std::ios::failbit);
return is;
}
static StudentInfo null_info;
struct Wrapper {
StudentInfo& _si;
Wrapper(StudentInfo& si = null_info) : _si(si) {}
operator StudentInfo&() const { return _si; }
friend std::ostream& operator<<(std::ostream& os, const Wrapper sis) {
for(auto& pair : sis._si)
os << std::quoted(pair.first) << ", " << pair.second << ';';
return os;
}
friend std::istream& operator>>(std::istream& is, const Wrapper sis) {
StudentInfo::value_type pair;
while (is >> std::skipws >> std::quoted(pair.first)) {
if (is >> Lit{','} >> pair.second >> Lit{';'})
sis._si.push_back(pair);
else
return is; // error here is bad
}
if (!is.bad()) // just eof this point is ok
is.clear();
return is;
}
};
template <typename T, typename Map> struct CoercePropertyMap : Map {
CoercePropertyMap(Map map) : Map(map){}
using value_type = T;
using reference = T;
};
template <typename T, typename Map>
CoercePropertyMap<T, Map> coerce_map(Map map) { return map; }
}
struct GraphItem {
std::string Division;
StudentInfo studentInfo;
};
using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::directedS, GraphItem>;
static inline boost::dynamic_properties DynProps(Graph& g) {
boost::dynamic_properties dp;
dp.property("Division", get(&GraphItem::Division, g));
dp.property("studentInfo", coerce_map<Wrapper>(get(&GraphItem::studentInfo, g)));
return dp;
}
Graph ReadGraph(std::string const &fileName) {
Graph graph;
auto dp = DynProps(graph);
std::ifstream file(fileName);
boost::read_graphml(file, graph, dp);
return graph;
}
void WriteGraph(std::ostream &os, Graph &graph) {
boost::write_graphml(os, graph, DynProps(graph), true);
}
void WriteGraph(std::string const& fileName, Graph &graph) {
std::ofstream ofs(fileName);
WriteGraph(ofs, graph);
}
#include <boost/graph/graph_utility.hpp>
namespace Gen { Graph graph(); } // generate random data
int main() {
{
Graph g = Gen::graph();
WriteGraph("input.txt", g);
}
Graph g = ReadGraph("input.txt");
print_graph(g, get(&GraphItem::Division, g));
// or as XML
WriteGraph(std::cout << "==== XML version: ====\n\n", g);
}
/// generate data
#include <boost/graph/random.hpp>
#include <boost/random.hpp>
#include <random>
namespace Gen {
namespace {
namespace R = boost::random;
R::mt19937 engine {42}; // { std::random_device{}() };
template <typename Range> auto sample(Range const &from) {
return *std::next(boost::begin(from), R::uniform_int_distribution<>(1, boost::size(from))(engine) - 1);
}
int age() { return R::uniform_int_distribution<>(18, 27)(engine); }
std::string division() {
static std::string const d[] = { "science", "drama", "mathematics", "philosophy", "alchemy" };
return sample(d);
}
std::string name() {
static std::string const f[] = { "John", "Daisy", "Chuck", "Mary-Anne", "Ernest", "Philomena", "Joyce", "James" };
static std::string const l[] = { "Joyce", "Habakuk", "Hornam", "Bufgloon", "Glinka", "Tarsinck", "Preet" };
return sample(f) + " " + sample(l);
}
StudentInfo studentInfo() {
StudentInfo si;
auto const n = R::uniform_int_distribution<>(2,5)(engine);
for (int i = 0; i < n; ++i)
si.emplace_back(name(), age());
return si;
}
}
Graph graph() {
Graph g;
boost::generate_random_graph(g, 5, 7, engine);
for (auto vd: boost::make_iterator_range(vertices(g)))
g[vd] = { division(), studentInfo() };
return g;
}
} // namespace Gen
Prints
philosophy --> drama
drama --> mathematics
drama --> philosophy
mathematics --> philosophy drama
drama --> philosophy drama
==== XML version: ====
<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
<key id="key0" for="node" attr.name="Division" attr.type="string" />
<key id="key1" for="node" attr.name="studentInfo" attr.type="string" />
<graph id="G" edgedefault="directed" parse.nodeids="canonical" parse.edgeids="canonical" parse.order="nodesfirst">
<node id="n0">
<data key="key0">philosophy</data>
<data key="key1">"James Joyce", 18;"James Tarsinck", 25;"Daisy Joyce", 20;"Ernest Habakuk", 27;</data>
</node>
<node id="n1">
<data key="key0">drama</data>
<data key="key1">"James Joyce", 18;"James Tarsinck", 25;"Daisy Joyce", 20;"Ernest Habakuk", 27;"Mary-Anne Joyce", 23;"Ernest Hornam", 18;"Daisy Hornam", 24;"James Hornam", 18;</data>
</node>
<node id="n2">
<data key="key0">drama</data>
<data key="key1">"James Joyce", 18;"James Tarsinck", 25;"Daisy Joyce", 20;"Ernest Habakuk", 27;"Mary-Anne Joyce", 23;"Ernest Hornam", 18;"Daisy Hornam", 24;"James Hornam", 18;"Joyce Joyce", 22;"Mary-Anne Habakuk", 24;</data>
</node>
<node id="n3">
<data key="key0">mathematics</data>
<data key="key1">"James Joyce", 18;"James Tarsinck", 25;"Daisy Joyce", 20;"Ernest Habakuk", 27;"Mary-Anne Joyce", 23;"Ernest Hornam", 18;"Daisy Hornam", 24;"James Hornam", 18;"Joyce Joyce", 22;"Mary-Anne Habakuk", 24;"John Bufgloon", 23;"Philomena Glinka", 26;"John Bufgloon", 19;"James Preet", 18;"Joyce Bufgloon", 27;</data>
</node>
<node id="n4">
<data key="key0">drama</data>
<data key="key1">"James Joyce", 18;"James Tarsinck", 25;"Daisy Joyce", 20;"Ernest Habakuk", 27;"Mary-Anne Joyce", 23;"Ernest Hornam", 18;"Daisy Hornam", 24;"James Hornam", 18;"Joyce Joyce", 22;"Mary-Anne Habakuk", 24;"John Bufgloon", 23;"Philomena Glinka", 26;"John Bufgloon", 19;"James Preet", 18;"Joyce Bufgloon", 27;"Daisy Joyce", 18;"Mary-Anne Habakuk", 24;"Ernest Joyce", 24;</data>
</node>
<edge id="e0" source="n0" target="n2">
</edge>
<edge id="e1" source="n1" target="n3">
</edge>
<edge id="e2" source="n2" target="n0">
</edge>
<edge id="e3" source="n3" target="n0">
</edge>
<edge id="e4" source="n3" target="n2">
</edge>
<edge id="e5" source="n4" target="n0">
</edge>
<edge id="e6" source="n4" target="n1">
</edge>
</graph>
</graphml>