Note that the easiest is to use the builtin ignore_other_properties
:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphml.hpp>
#include <fstream>
int main() {
boost::adjacency_list<> g;
boost::dynamic_properties props(boost::ignore_other_properties);
{
std::ifstream input_stream("input.xml");
read_graphml(input_stream, g, props);
}
write_graphml(std::cout, g, props);
}
Given input xml of
<?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="Name" attr.type="string" />
<graph id="G" edgedefault="directed" parse.nodeids="canonical" parse.edgeids="canonical" parse.order="nodesfirst">
<node id="n0">
<data key="key0">A</data>
</node>
<node id="n1">
<data key="key0">D</data>
</node>
<node id="n2">
<data key="key0">B</data>
</node>
<node id="n3">
<data key="key0">C</data>
</node>
<edge id="e0" source="n0" target="n1">
</edge>
<edge id="e1" source="n2" target="n3">
</edge>
</graph>
</graphml>
Prints
<?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">
<graph id="G" edgedefault="directed" parse.nodeids="free" parse.edgeids="canonical" parse.order="nodesfirst">
<node id="n0">
</node>
<node id="n1">
</node>
<node id="n2">
</node>
<node id="n3">
</node>
<edge id="e0" source="n0" target="n1">
</edge>
<edge id="e1" source="n2" target="n3">
</edge>
</graph>
</graphml>
Caveats!
Has a few caveats.
Your graph model is not equipped with storage for any properties, so I'll assume you want them to be external.
Your graph model assumes directionality and also forbids duplicate edges. This may not be what you need.
Graphviz read/write will not roundtrip. That is, comments will be gone, and edges and nodes will have arbitrarily different keys in the ML. Of course, that should in principle not matter a lot, but depending on your expectations/requirements may be a deal breaker.
Your graph model cannot even be written without an external vertex_index
property, since your choice for a node-based vertex container (setS
) does not have an implicit one.
How Would You Do It?
I'll drop the opinionated setS
choice because of the drawbacks listed above. Note that we still assume directionality. I'll leave that for you to "fix".
A search from my existing answers shows how to use dynamic_properties with dynamic maps. Adapting to your question:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphml.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <fstream>
using Attributes = std::map<std::string, std::string>;
using DynMap = boost::shared_ptr<boost::dynamic_property_map>;
using Graph = boost::adjacency_list<>;
using V = Graph::vertex_descriptor;
using E = Graph::edge_descriptor;
static inline auto make_dyn(auto m) -> DynMap {
using DM = boost::detail::dynamic_property_map_adaptor<decltype(m)>;
auto sp = boost::make_shared<DM>(m);
return boost::static_pointer_cast<boost::dynamic_property_map>(sp);
};
int main() {
std::map<V, Attributes> va;
std::map<E, Attributes> ea;
boost::dynamic_properties props(
[vamap = boost::make_assoc_property_map(va),
eamap = boost::make_assoc_property_map(ea)] //
(std::string const& name, auto&& descriptor, auto&&) -> DynMap {
if (typeid(V) == descriptor.type()) {
return make_dyn(boost::make_function_property_map<V>(
[=](V v) -> std::string& { return vamap[v][name]; }));
} else {
return make_dyn(boost::make_function_property_map<E>(
[=](E e) -> std::string& { return eamap[e][name]; }));
}
});
Graph g;
std::ifstream input_stream("input.xml");
read_graphml(input_stream, g, props);
write_graphml(std::cout, g, props);
}
Now retains the Name
attributes as expected:
<?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="Name" attr.type="string" />
<graph id="G" edgedefault="directed" parse.nodeids="free" parse.edgeids="canonical" parse.order="nodesfirst">
<node id="n0">
<data key="key0">A</data>
</node>
<node id="n1">
<data key="key0">D</data>
</node>
<node id="n2">
<data key="key0">B</data>
</node>
<node id="n3">
<data key="key0">C</data>
</node>
<edge id="e0" source="n0" target="n1">
</edge>
<edge id="e1" source="n2" target="n3">
</edge>
</graph>
</graphml>
UPDATE: Strongly Typed?
In response to the comment, I added a strongly typed mapper.
It turns out pretty elegant because I kind of "flipped around" the storage making the whole variant access unnecessary (except for storage). The resulting data structures are much harder to use in your own code, that elegance is bit of lie, be warned.
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphml.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <boost/variant.hpp>
#include <fstream>
using DynMap = boost::shared_ptr<boost::dynamic_property_map>;
using Graph = boost::adjacency_list<>;
using V = Graph::vertex_descriptor;
using E = Graph::edge_descriptor;
using Name = std::string;
template <typename DescriptorT, typename T> using AttrMap = std::map<DescriptorT, T>;
template <typename DescriptorT, typename... Ts>
using VarAttr = std::map<Name, boost::variant<AttrMap<DescriptorT, Ts>...>>;
using VSupported = VarAttr<V, std::string, double>;
using ESupported = VarAttr<E, std::string, double>;
template <typename VorE, typename... Ts>
inline DynMap instantiate(VarAttr<VorE, Ts...>& supported, Name const& name,
std::type_info const& type) {
auto match_type = [&]<typename T>() -> DynMap {
if (type != typeid(T))
return nullptr; // non-matching
// emplace the appropriately typed map
using Attr = AttrMap<VorE, T>;
auto& [_, variant] = *supported.emplace(name, Attr()).first;
auto& instance = boost::get<Attr>(variant);
return boost::make_shared<
boost::detail::dynamic_property_map_adaptor<boost::associative_property_map<Attr>>>(
instance);
};
for (auto& matched : {match_type.template operator()<Ts>()...})
if (matched)
return matched;
return {};
};
int main() {
VSupported va;
ESupported ea;
boost::dynamic_properties props(
[&](std::string const& name, auto&& descriptor, auto&& value) -> DynMap {
return typeid(V) == descriptor.type() ? instantiate<V>(va, name, value.type())
: instantiate<E>(ea, name, value.type());
});
Graph g;
std::ifstream input_stream("input.xml");
read_graphml(input_stream, g, props);
props.property("Label", boost::make_function_property_map<E>([](E e) {
return boost::lexical_cast<std::string>(e);
}));
write_graphml(std::cout, g, props);
}
When adding a double
attribute to edges:
<key id="key1" for="edge" attr.name="Weight" attr.type="double" />
With data like
<edge id="e0" source="n0" target="n1">
<data key="key1">12.3E-2</data>
</edge>
<edge id="e1" source="n2" target="n3">
<data key="key1">23.4E-2</data>
</edge>
Now prints output:
<edge id="e0" source="n0" target="n1">
<data key="key0">(0,1)</data>
<data key="key2">0.123</data>
</edge>
<edge id="e1" source="n2" target="n3">
<data key="key0">(2,3)</data>
<data key="key2">0.234</data>
</edge>
Showing that the attributes are not strings anymore.
