c++graphboostgraphmlboost-property-map

How to interpret complex strings as graph properties when reading a graphML file using `boost::read_graphml`?


I have a graph type where each Vertex carries a std::vector<int> as a property.

struct VertexProperties {
  std::vector<int> numbers;
};
using Graph = boost::adjacency_list<
    boost::vecS, boost::vecS, boost::undirectedS, VertexProperties>;

I wrote an example object of my graph type to a GraphML file using boost::write_graphml. To do so, I used boost::make_transform_value_property_map to convert the std::vector<int> property to a std::string. The GraphML file has the following contents:

<?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="numbers" attr.type="string" />
  <graph id="G" edgedefault="undirected" parse.nodeids="free" parse.edgeids="canonical" parse.order="nodesfirst">
    <node id="n0">
      <data key="key0">1 2 3 </data>
    </node>
  </graph>
</graphml>

Now I would like to read the file back in to reobtain the graph (in a different program) using boost::read_graphml. To do so, it is necessary to create a boost::dynamic_properties object and add to that a property map that can understand the information found in the GraphML file and set the correct vertex property accordingly.

How can the latter property map be defined?


Solution

  • I solved my problem by writing a custom property map class template TranslateStringPMap that wraps an existing property map and takes two function objects that convert between strings and the wrapped map's value type.

    File translate_string_pmap.hpp:

    #ifndef TRANSLATE_STRING_PMAP_H
    #define TRANSLATE_STRING_PMAP_H
    
    #include <string>
    
    #include <boost/property_map/property_map.hpp>
    
    template <typename PMap, typename ToString, typename FromString>
    class TranslateStringPMap {
    public:
      using category = boost::read_write_property_map_tag;
      using key_type = typename boost::property_traits<PMap>::key_type;
      using reference = std::string;
      using value_type = std::string;
    
      TranslateStringPMap(
          PMap wrapped_pmap, ToString to_string, FromString from_string)
          : wrapped_pmap{wrapped_pmap},
            to_string{to_string},
            from_string{from_string} {}
    
      auto friend get(TranslateStringPMap const& translator, key_type const& key)
          -> value_type {
        return translator.to_string(get(translator.wrapped_pmap, key));
      }
    
      auto friend put(
          TranslateStringPMap const& translator, key_type const& key,
          value_type const& value) -> void {
        boost::put(translator.wrapped_pmap, key, translator.from_string(value));
      }
    
    private:
      PMap wrapped_pmap;
      ToString to_string;
      FromString from_string;
    };
    
    #endif
    

    By customizing the conversion function objects to_string and from_string, TranslateStringPMap can be added to a boost::dynamic_properties object to facilitate reading and writing of arbitrary graph property types. The following file gives a usage example.

    File graph_rw.cpp:

    #include <sstream>
    #include <string>
    #include <vector>
    
    #include <boost/graph/adjacency_list.hpp>
    #include <boost/graph/graphml.hpp>
    #include <boost/property_map/dynamic_property_map.hpp>
    
    #include "translate_string_pmap.hpp"
    
    struct VertexProperties {
      std::vector<int> numbers;
    };
    using Graph = boost::adjacency_list<
        boost::vecS, boost::vecS, boost::undirectedS, VertexProperties>;
    
    auto vec2str(std::vector<int> const& vec) -> std::string {
      auto str = std::string{};
      for (auto number : vec) {
        str += std::to_string(number) += " ";
      }
      return str;
    }
    
    auto str2vec(std::string const& str) -> std::vector<int> {
      auto strs = std::stringstream{str};
      auto number = 0;
      auto vec = std::vector<int>{};
      while (strs >> number) {
        vec.push_back(number);
      }
      return vec;
    }
    
    auto write_my_graphml(Graph& graph, std::ofstream& output_stream) -> void {
      auto dprops = boost::dynamic_properties{};
      dprops.property(
          "numbers",
          TranslateStringPMap{
              boost::get(&VertexProperties::numbers, graph), vec2str, str2vec});
      boost::write_graphml(output_stream, graph, dprops);
    }
    
    auto read_my_graphml(std::ifstream& input_stream) -> Graph {
      auto graph = Graph{};
      auto dprops = boost::dynamic_properties{};
      dprops.property(
          "numbers",
          TranslateStringPMap{
              boost::get(&VertexProperties::numbers, graph), vec2str, str2vec});
      boost::read_graphml(input_stream, graph, dprops);
      return graph;
    }
    
    auto main() -> int {
      {
        auto graph1 = Graph{};
        boost::add_vertex(VertexProperties{{1, 2, 3}}, graph1);
        auto out_stream = std::ofstream{"graph1.gml"};
        write_my_graphml(graph1, out_stream);
      }
      {
        auto in_stream = std::ifstream{"graph1.gml"};
        auto graph2 = read_my_graphml(in_stream);
        auto out_stream = std::ofstream{"graph2.gml"};
        write_my_graphml(graph2, out_stream);
      }
    }