From 78d2fd885f2bdeb8dd0407f47596c38d549d72c1 Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Mon, 29 Jan 2007 22:14:41 +0000 Subject: [PATCH] GraphML parser, from Tiago de Paula Peixoto [SVN r36841] --- build/Jamfile.v2 | 30 +++ doc/history.html | 3 +- doc/read_graphml.html | 161 ++++++++++++++ doc/read_graphml.rst | 158 ++++++++++++++ doc/table_of_contents.html | 3 +- doc/write_graphml.html | 258 +++++++++++++++++++++++ doc/write_graphml.rst | 256 +++++++++++++++++++++++ include/boost/graph/graphml.hpp | 332 +++++++++++++++++++++++++++++ src/graphml.cpp | 347 +++++++++++++++++++++++++++++++ test/Jamfile.v2 | 10 +- test/graphml_test.cpp | 81 ++++++++ test/graphml_test.xml | 52 +++++ test/transitive_closure_test.cpp | 1 + 13 files changed, 1689 insertions(+), 3 deletions(-) create mode 100644 doc/read_graphml.html create mode 100644 doc/read_graphml.rst create mode 100644 doc/write_graphml.html create mode 100644 doc/write_graphml.rst create mode 100644 include/boost/graph/graphml.hpp create mode 100644 src/graphml.cpp create mode 100644 test/graphml_test.cpp create mode 100644 test/graphml_test.xml diff --git a/build/Jamfile.v2 b/build/Jamfile.v2 index 231efcc2..af30af7b 100644 --- a/build/Jamfile.v2 +++ b/build/Jamfile.v2 @@ -4,9 +4,37 @@ project boost/graph : source-location ../src ; +local optional_sources ; +local optional_reqs ; + +if [ modules.peek : EXPAT_INCLUDE ] && [ modules.peek : EXPAT_LIBPATH ] +{ + local EXPAT_INCLUDE = [ modules.peek : EXPAT_INCLUDE ] ; + local EXPAT_LIBPATH = [ modules.peek : EXPAT_LIBPATH ] ; + + if --debug-configuration in [ modules.peek : ARGV ] + { + ECHO "Expat include directory: $(EXPAT_INCLUDE)" ; + ECHO "Expat library directory: $(EXPAT_LIBPATH)" ; + } + + optional_sources += graphml.cpp ; + optional_reqs += $(EXPAT_INCLUDE) + $(EXPAT_LIBPATH) + expat + ; +} +else +{ + ECHO "warning: Graph library does not contain optional GraphML reader." ; + ECHO "note: to enable GraphML support, set EXPAT_INCLUDE and EXPAT_LIBPATH to the" ; + ECHO "note: directories containing the Expat headers and libraries, respectively." ; +} + lib boost_graph : read_graphviz_spirit.cpp + $(optional_sources) : BOOST_GRAPH_NO_LIB=1 shared:BOOST_GRAPH_DYN_LINK=1 @@ -15,5 +43,7 @@ lib boost_graph # Without these flags, MSVC 7.1 and 8.0 crash msvc-7.1:-GR- msvc-8.0:-GR- + # Requirements for any "optional" sources + $(optional_reqs) ; diff --git a/doc/history.html b/doc/history.html index 48407df0..0c5e47f4 100644 --- a/doc/history.html +++ b/doc/history.html @@ -83,7 +83,8 @@ September 27, 2000.
  • Version 1.35.0
    New algorithms and components
    Enhancements
    • LEDA Adaptor improvements, from Jens Müller.
    • diff --git a/doc/read_graphml.html b/doc/read_graphml.html new file mode 100644 index 00000000..87d096d2 --- /dev/null +++ b/doc/read_graphml.html @@ -0,0 +1,161 @@ + + + + + + +Boost read_graphml + + + +

      Boost read_graphml

      +
      +
      +void read_graphml(std::istream& in, MutableGraph& graph,
      +                  dynamic_properties& dp);
      +
      +

      The read_graphml function interprets a graph described using the +graphml format and builds a BGL graph that captures that +description. Using this function, you can initialize a graph using +data stored as text.

      +

      The graphml format can specify both directed and undirected graphs, and +read_graphml differentiates between the two. One must pass +read_graphml an undirected graph when reading an undirected graph; +the same is true for directed graphs. Furthermore, read_graphml +will throw an exception if it encounters parallel edges and cannot add +them to the graph.

      +

      To handle attributes expressed in the graphml format, read_graphml +takes a dynamic_properties object and operates on its collection of +property maps. The reader passes all the properties encountered to +this object, using the graphml attribute names as the property keys, +and with the appropriate C++ type based on the graphml attribute type +definition.

      +
      +
      Requirements:
      +
        +
      • The type of the graph must model the Mutable Graph concept.
      • +
      • The type of the iterator must model the Multi-Pass Iterator +concept.
      • +
      • The property map value types must be default-constructible.
      • +
      +
      +
      + +
      +

      Where Defined

      +

      <boost/graph/graphml.hpp>

      +
      +
      +

      Exceptions

      +
      +struct graph_exception : public std::exception {
      +  virtual ~graph_exception() throw();
      +  virtual const char* what() const throw() = 0;
      +};
      +
      +struct bad_parallel_edge : public graph_exception {
      +  std::string from;
      +  std::string to;
      +
      +  bad_parallel_edge(const std::string&, const std::string&);
      +  virtual ~bad_parallel_edge() throw();
      +  const char* what() const throw();
      +};
      +
      +struct directed_graph_error : public graph_exception {
      +  virtual ~directed_graph_error() throw();
      +  virtual const char* what() const throw();
      +};
      +
      +struct undirected_graph_error : public graph_exception {
      +  virtual ~undirected_graph_error() throw();
      +  virtual const char* what() const throw();
      +};
      +
      +struct parse_error : public graph_exception {
      +  parse_error(const std::string&);
      +  virtual ~parse_error() throw() {}
      +  virtual const char* what() const throw();
      +  std::string statement;
      +};
      +
      +

      Under certain circumstances, read_graphml will throw one of the +above exceptions. The three concrete exceptions can all be caught +using the general graph_exception moniker when greater precision +is not needed. In addition, all of the above exceptions derive from +the standard std::exception for even more generalized error +handling.

      +

      The bad_parallel_edge exception is thrown when an attempt to add a +parallel edge to the supplied MutableGraph fails. The graphml format +supports parallel edges, but some BGL-compatible graph types do not. +One example of such a graph is boost::adjacency_list<setS,vecS>, +which allows at most one edge can between any two vertices.

      +

      The directed_graph_error exception occurs when an undirected graph +type is passed to read_graph but the textual representation of the +graph is directed, as indicated by the edgedefault="directed" +graph attribute in the graphml format.

      +

      The undirected_graph_error exception occurs when a directed graph +type is passed to read_graph but the textual representation of the +graph is undirected, as indicated by the edgedefault="undirected" +graph attribute in the graphml format.

      +
      +
      +

      Building the graphml reader

      +

      To use the graphml reader, you will need to build and link against +the "bgl-graphml" library. The library can be built by following the +Boost Jam Build Instructions for the subdirectory libs/graph/build.

      +
      +
      +

      Notes

      +
      +
        +
      • On successful reading of a graph, every vertex and edge will have +an associated value for every respective edge and vertex property +encountered while interpreting the graph. These values will be set +using the dynamic_properties object. Some properties may be +put multiple times during the course of reading in order to +ensure the graphml semantics. Those edges and vertices that are +not explicitly given a value for a property (and that property has +no default) will be given the default constructed value of the +value type. Be sure that property map value types are default +constructible.
      • +
      • Nested graphs are supported as long as they are exactly of the same +type as the root graph, i.e., are also directed or undirected. Note +that since nested graphs are not directly supported by BGL, they +are in fact completely ignored when building the graph, and the +internal vertices or edges are interpreted as belonging to the root +graph.
      • +
      • Hyperedges and Ports are not supported.
      • +
      +
      +
      + +
      +

      Future Work

      +
      +
        +
      • Better expat error detection.
      • +
      +
      +
      +
      + + + + diff --git a/doc/read_graphml.rst b/doc/read_graphml.rst new file mode 100644 index 00000000..90c3de2a --- /dev/null +++ b/doc/read_graphml.rst @@ -0,0 +1,158 @@ +============================ +|(logo)|__ ``read_graphml`` +============================ + +.. |(logo)| image:: ../../../boost.png + :align: middle + :alt: Boost + +__ ../../../index.htm + +:: + + void read_graphml(std::istream& in, MutableGraph& graph, + dynamic_properties& dp); + + +The ``read_graphml`` function interprets a graph described using the +graphml_ format and builds a BGL graph that captures that +description. Using this function, you can initialize a graph using +data stored as text. + +The graphml format can specify both directed and undirected graphs, and +``read_graphml`` differentiates between the two. One must pass +``read_graphml`` an undirected graph when reading an undirected graph; +the same is true for directed graphs. Furthermore, ``read_graphml`` +will throw an exception if it encounters parallel edges and cannot add +them to the graph. + +To handle attributes expressed in the graphml format, ``read_graphml`` +takes a dynamic_properties_ object and operates on its collection of +property maps. The reader passes all the properties encountered to +this object, using the graphml attribute names as the property keys, +and with the appropriate C++ type based on the graphml attribute type +definition. + +Requirements: + - The type of the graph must model the `Mutable Graph`_ concept. + - The type of the iterator must model the `Multi-Pass Iterator`_ + concept. + - The property map value types must be default-constructible. + + +.. contents:: + +Where Defined +------------- +```` + +Exceptions +---------- + +:: + + struct graph_exception : public std::exception { + virtual ~graph_exception() throw(); + virtual const char* what() const throw() = 0; + }; + + struct bad_parallel_edge : public graph_exception { + std::string from; + std::string to; + + bad_parallel_edge(const std::string&, const std::string&); + virtual ~bad_parallel_edge() throw(); + const char* what() const throw(); + }; + + struct directed_graph_error : public graph_exception { + virtual ~directed_graph_error() throw(); + virtual const char* what() const throw(); + }; + + struct undirected_graph_error : public graph_exception { + virtual ~undirected_graph_error() throw(); + virtual const char* what() const throw(); + }; + + struct parse_error : public graph_exception { + parse_error(const std::string&); + virtual ~parse_error() throw() {} + virtual const char* what() const throw(); + std::string statement; + }; + +Under certain circumstances, ``read_graphml`` will throw one of the +above exceptions. The three concrete exceptions can all be caught +using the general ``graph_exception`` moniker when greater precision +is not needed. In addition, all of the above exceptions derive from +the standard ``std::exception`` for even more generalized error +handling. + +The ``bad_parallel_edge`` exception is thrown when an attempt to add a +parallel edge to the supplied MutableGraph fails. The graphml format +supports parallel edges, but some BGL-compatible graph types do not. +One example of such a graph is ``boost::adjacency_list``, +which allows at most one edge can between any two vertices. + + +The ``directed_graph_error`` exception occurs when an undirected graph +type is passed to ``read_graph`` but the textual representation of the +graph is directed, as indicated by the ``edgedefault="directed"`` +graph attribute in the graphml format. + +The ``undirected_graph_error`` exception occurs when a directed graph +type is passed to ``read_graph`` but the textual representation of the +graph is undirected, as indicated by the ``edgedefault="undirected"`` +graph attribute in the graphml format. + + +Building the graphml reader +----------------------------- +To use the graphml reader, you will need to build and link against +the "bgl-graphml" library. The library can be built by following the +`Boost Jam Build Instructions`_ for the subdirectory ``libs/graph/build``. + + +Notes +----- + + - On successful reading of a graph, every vertex and edge will have + an associated value for every respective edge and vertex property + encountered while interpreting the graph. These values will be set + using the ``dynamic_properties`` object. Some properties may be + ``put`` multiple times during the course of reading in order to + ensure the graphml semantics. Those edges and vertices that are + not explicitly given a value for a property (and that property has + no default) will be given the default constructed value of the + value type. **Be sure that property map value types are default + constructible.** + + - Nested graphs are supported as long as they are exactly of the same + type as the root graph, i.e., are also directed or undirected. Note + that since nested graphs are not directly supported by BGL, they + are in fact completely ignored when building the graph, and the + internal vertices or edges are interpreted as belonging to the root + graph. + + - Hyperedges and Ports are not supported. + +See Also +-------- + +write_graphml_ + + +Future Work +----------- + + - Better expat error detection. + + +.. _Graphml: http://graphml.graphdrawing.org/ + +.. _`Mutable Graph`: MutableGraph.html +.. _`Multi-Pass Iterator`: ../../iterator/index.html +.. _dynamic_properties: ../../property_map/doc/dynamic_property_map.html +.. _write_graphml: write_graphml.html +.. _Boost Jam Build Instructions: ../../../more/getting_started.html#Build_Install \ No newline at end of file diff --git a/doc/table_of_contents.html b/doc/table_of_contents.html index abe87b0d..a0ae2fbf 100644 --- a/doc/table_of_contents.html +++ b/doc/table_of_contents.html @@ -221,7 +221,8 @@
    • Graph Input/Output
      1. AT&T Graphviz: read_graphviz, write_graphviz
      2. -
      3. DIMACS Max-flow: read_dimacs, write_dimacs
      4. +
      5. DIMACS Max-flow: read_dimacs_max_flow, write_dimacs
      6. +
      7. GraphML: read_graphml and write_graphml
    • Auxiliary Concepts, Classes, and Functions diff --git a/doc/write_graphml.html b/doc/write_graphml.html new file mode 100644 index 00000000..332254e4 --- /dev/null +++ b/doc/write_graphml.html @@ -0,0 +1,258 @@ + + + + + + +Boost write_graphml + + + +

      Boost write_graphml

      +
      +
      +template<typename Graph>
      +void
      +write_graphml(std::ostream& out, const Graph& g, const dynamic_properties& dp, 
      +              bool ordered_vertices=false);
      +
      +template<typename Graph, typename VertexIndexMap>
      +void
      +write_graphml(std::ostream& out, const Graph& g, VertexIndexMap vertex_index,
      +              const dynamic_properties& dp, bool ordered_vertices=false);
      +
      +

      This is to write a BGL graph object into an output stream in the +graphml format. Both overloads of write_graphml will emit all of +the properties stored in the dynamic_properties object, thereby +retaining the properties that have been read in through the dual +function read_graphml. The second overload must be used when the +graph doesn't have an internal vertex index map, which must then be +supplied with the appropriate parameter.

      + +
      +

      Where Defined

      +

      <boost/graph/graphml.hpp>

      +
      +
      +

      Parameters

      +
      +
      OUT: std::ostream& out
      +
      A standard std::ostream object.
      +
      IN: VertexListGraph& g
      +
      A directed or undirected graph. The +graph's type must be a model of VertexListGraph. If the graph +doesn't have an internal vertex_index property map, one +must be supplied with the vertex_index parameter.
      +
      IN: VertexIndexMap vertex_index
      +
      A vertex property map containing the indexes in the range +[0,num_vertices(g)].
      +
      IN: dynamic_properties& dp
      +
      Contains all of the vertex and edge properties that should be +emitted by the graphml writer.
      +
      IN: bool ordered_vertices
      +
      This tells whether or not the order of the vertices from vertices(g) +matches the order of the indexes. If true, the parse.nodeids +graph attribute will be set to canonical. Otherwise it will be +set to free.
      +
      +
      +
      +

      Example

      +

      This example demonstrates using BGL-graphml interface to write +a BGL graph into a graphml format file.

      +
      +enum files_e { dax_h, yow_h, boz_h, zow_h, foo_cpp,
      +               foo_o, bar_cpp, bar_o, libfoobar_a,
      +               zig_cpp, zig_o, zag_cpp, zag_o,
      +               libzigzag_a, killerapp, N };
      +const char* name[] = { "dax.h", "yow.h", "boz.h", "zow.h", "foo.cpp",
      +                       "foo.o", "bar.cpp", "bar.o", "libfoobar.a",
      +                       "zig.cpp", "zig.o", "zag.cpp", "zag.o",
      +                       "libzigzag.a", "killerapp" };
      +
      +int main(int,char*[])
      +{
      +    typedef pair<int,int> Edge;
      +    Edge used_by[] = {
      +        Edge(dax_h, foo_cpp), Edge(dax_h, bar_cpp), Edge(dax_h, yow_h),
      +        Edge(yow_h, bar_cpp), Edge(yow_h, zag_cpp),
      +        Edge(boz_h, bar_cpp), Edge(boz_h, zig_cpp), Edge(boz_h, zag_cpp),
      +        Edge(zow_h, foo_cpp),
      +        Edge(foo_cpp, foo_o),
      +        Edge(foo_o, libfoobar_a),
      +        Edge(bar_cpp, bar_o),
      +        Edge(bar_o, libfoobar_a),
      +        Edge(libfoobar_a, libzigzag_a),
      +        Edge(zig_cpp, zig_o),
      +        Edge(zig_o, libzigzag_a),
      +        Edge(zag_cpp, zag_o),
      +        Edge(zag_o, libzigzag_a),
      +        Edge(libzigzag_a, killerapp)
      +     };
      +
      +    const int nedges = sizeof(used_by)/sizeof(Edge);
      +
      +    typedef adjacency_list< vecS, vecS, directedS,
      +        property< vertex_color_t, string >,
      +        property< edge_weight_t, int >
      +        > Graph;
      +    Graph g(used_by, used_by + nedges, N);
      +
      +    graph_traits<Graph>::vertex_iterator v, v_end;
      +    for (tie(v,v_end) = vertices(g); v != v_end; ++v)
      +        put(vertex_color_t(), g, *v, name[*v]);
      +
      +    graph_traits<Graph>::edge_iterator e, e_end;
      +    for (tie(e,e_end) = edges(g); e != e_end; ++e)
      +        put(edge_weight_t(), g, *e, 3);
      +
      +    dynamic_properties dp;
      +    dp.property("name", get(vertex_color_t(), g));
      +    dp.property("weight", get(edge_weight_t(), g));
      +
      +    write_graphml(std::cout, g, dp, true);
      + }
      +
      +

      The output will be:

      +
      +<?xml version="1.0" encoding="UTF-8"?>
      +<graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://graphml.graphdrawing.org/xmlns/graphml/graphml-attributes-1.0rc.xsd">
      +  <key id="key0" for="node" attr.name="name" attr.type="string" />
      +  <key id="key1" for="edge" attr.name="weight" attr.type="int" />
      +  <graph id="G" edgedefault="directed" parse.nodeids="canonical" parse.edgeids="canonical" parse.order="nodesfirst">
      +    <node id="n0">
      +      <data key="key0">dax.h</data>
      +    </node>
      +    <node id="n1">
      +      <data key="key0">yow.h</data>
      +    </node>
      +    <node id="n2">
      +      <data key="key0">boz.h</data>
      +    </node>
      +    <node id="n3">
      +      <data key="key0">zow.h</data>
      +    </node>
      +    <node id="n4">
      +      <data key="key0">foo.cpp</data>
      +    </node>
      +    <node id="n5">
      +      <data key="key0">foo.o</data>
      +    </node>
      +    <node id="n6">
      +      <data key="key0">bar.cpp</data>
      +    </node>
      +    <node id="n7">
      +      <data key="key0">bar.o</data>
      +    </node>
      +    <node id="n8">
      +      <data key="key0">libfoobar.a</data>
      +    </node>
      +    <node id="n9">
      +      <data key="key0">zig.cpp</data>
      +    </node>
      +    <node id="n10">
      +      <data key="key0">zig.o</data>
      +    </node>
      +    <node id="n11">
      +      <data key="key0">zag.cpp</data>
      +    </node>
      +    <node id="n12">
      +      <data key="key0">zag.o</data>
      +    </node>
      +    <node id="n13">
      +      <data key="key0">libzigzag.a</data>
      +    </node>
      +    <node id="n14">
      +      <data key="key0">killerapp</data>
      +    </node>
      +    <edge id="e0" source="n0" target="n4">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e1" source="n0" target="n6">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e2" source="n0" target="n1">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e3" source="n1" target="n6">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e4" source="n1" target="n11">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e5" source="n2" target="n6">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e6" source="n2" target="n9">
      +       <data key="key1">3</data>
      +    </edge>
      +    <edge id="e7" source="n2" target="n11">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e8" source="n3" target="n4">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e9" source="n4" target="n5">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e10" source="n5" target="n8">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e11" source="n6" target="n7">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e12" source="n7" target="n8">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e13" source="n8" target="n13">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e14" source="n9" target="n10">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e15" source="n10" target="n13">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e16" source="n11" target="n12">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e17" source="n12" target="n13">
      +      <data key="key1">3</data>
      +    </edge>
      +    <edge id="e18" source="n13" target="n14">
      +      <data key="key1">3</data>
      +    </edge>
      +  </graph>
      +</graphml>
      +
      +
      +
      +

      See Also

      +

      _read_graphml

      +
      +
      +

      Notes

      +
      +
        +
      • Note that you can use graphml file write facilities without linking +against the boost_graph library.
      • +
      +
      +
      +
      + + + + diff --git a/doc/write_graphml.rst b/doc/write_graphml.rst new file mode 100644 index 00000000..82919170 --- /dev/null +++ b/doc/write_graphml.rst @@ -0,0 +1,256 @@ +============================ +|(logo)|__ ``write_graphml`` +============================ + +.. |(logo)| image:: ../../../boost.png + :align: middle + :alt: Boost + +__ ../../../index.htm + +:: + + template + void + write_graphml(std::ostream& out, const Graph& g, const dynamic_properties& dp, + bool ordered_vertices=false); + + template + void + write_graphml(std::ostream& out, const Graph& g, VertexIndexMap vertex_index, + const dynamic_properties& dp, bool ordered_vertices=false); + +This is to write a BGL graph object into an output stream in the +graphml_ format. Both overloads of ``write_graphml`` will emit all of +the properties stored in the dynamic_properties_ object, thereby +retaining the properties that have been read in through the dual +function read_graphml_. The second overload must be used when the +graph doesn't have an internal vertex index map, which must then be +supplied with the appropriate parameter. + +.. contents:: + +Where Defined +------------- +```` + +Parameters +---------- + +OUT: ``std::ostream& out`` + A standard ``std::ostream`` object. + +IN: ``VertexListGraph& g`` + A directed or undirected graph. The + graph's type must be a model of VertexListGraph_. If the graph + doesn't have an internal ``vertex_index`` property map, one + must be supplied with the vertex_index parameter. + +IN: ``VertexIndexMap vertex_index`` + A vertex property map containing the indexes in the range + [0,num_vertices(g)]. + + +IN: ``dynamic_properties& dp`` + Contains all of the vertex and edge properties that should be + emitted by the graphml writer. + +IN: ``bool ordered_vertices`` + This tells whether or not the order of the vertices from vertices(g) + matches the order of the indexes. If ``true``, the ``parse.nodeids`` + graph attribute will be set to ``canonical``. Otherwise it will be + set to ``free``. + + + +Example +------- + +This example demonstrates using BGL-graphml interface to write +a BGL graph into a graphml format file. + +:: + + enum files_e { dax_h, yow_h, boz_h, zow_h, foo_cpp, + foo_o, bar_cpp, bar_o, libfoobar_a, + zig_cpp, zig_o, zag_cpp, zag_o, + libzigzag_a, killerapp, N }; + const char* name[] = { "dax.h", "yow.h", "boz.h", "zow.h", "foo.cpp", + "foo.o", "bar.cpp", "bar.o", "libfoobar.a", + "zig.cpp", "zig.o", "zag.cpp", "zag.o", + "libzigzag.a", "killerapp" }; + + int main(int,char*[]) + { + typedef pair Edge; + Edge used_by[] = { + Edge(dax_h, foo_cpp), Edge(dax_h, bar_cpp), Edge(dax_h, yow_h), + Edge(yow_h, bar_cpp), Edge(yow_h, zag_cpp), + Edge(boz_h, bar_cpp), Edge(boz_h, zig_cpp), Edge(boz_h, zag_cpp), + Edge(zow_h, foo_cpp), + Edge(foo_cpp, foo_o), + Edge(foo_o, libfoobar_a), + Edge(bar_cpp, bar_o), + Edge(bar_o, libfoobar_a), + Edge(libfoobar_a, libzigzag_a), + Edge(zig_cpp, zig_o), + Edge(zig_o, libzigzag_a), + Edge(zag_cpp, zag_o), + Edge(zag_o, libzigzag_a), + Edge(libzigzag_a, killerapp) + }; + + const int nedges = sizeof(used_by)/sizeof(Edge); + + typedef adjacency_list< vecS, vecS, directedS, + property< vertex_color_t, string >, + property< edge_weight_t, int > + > Graph; + Graph g(used_by, used_by + nedges, N); + + graph_traits::vertex_iterator v, v_end; + for (tie(v,v_end) = vertices(g); v != v_end; ++v) + put(vertex_color_t(), g, *v, name[*v]); + + graph_traits::edge_iterator e, e_end; + for (tie(e,e_end) = edges(g); e != e_end; ++e) + put(edge_weight_t(), g, *e, 3); + + dynamic_properties dp; + dp.property("name", get(vertex_color_t(), g)); + dp.property("weight", get(edge_weight_t(), g)); + + write_graphml(std::cout, g, dp, true); + } + + +The output will be: + +:: + + + + + + + + dax.h + + + yow.h + + + boz.h + + + zow.h + + + foo.cpp + + + foo.o + + + bar.cpp + + + bar.o + + + libfoobar.a + + + zig.cpp + + + zig.o + + + zag.cpp + + + zag.o + + + libzigzag.a + + + killerapp + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + +See Also +-------- + +_read_graphml + +Notes +----- + + - Note that you can use graphml file write facilities without linking + against the ``boost_graph`` library. + +.. _graphml: http://graphml.graphdrawing.org/ + +.. _dynamic_properties: ../../property_map/doc/dynamic_property_map.html +.. _read_graphml: read_graphml.html +.. _VertexListGraph: VertexListGraph.html \ No newline at end of file diff --git a/include/boost/graph/graphml.hpp b/include/boost/graph/graphml.hpp new file mode 100644 index 00000000..be7a804e --- /dev/null +++ b/include/boost/graph/graphml.hpp @@ -0,0 +1,332 @@ +// Copyright (C) 2006 Tiago de Paula Peixoto +// Copyright (C) 2004 The Trustees of Indiana University. +// +// Use, modification and distribution is subject to the Boost Software +// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// Authors: Douglas Gregor +// Andrew Lumsdaine +// Tiago de Paula Peixoto + +#ifndef BOOST_GRAPH_GRAPHML_HPP +#define BOOST_GRAPH_GRAPHML_HPP + +#include +#include +#include +#include +#include // for exceptions +#include +#include +#include +#include +#include +#include +#include + +namespace boost +{ + +///////////////////////////////////////////////////////////////////////////// +// Graph reader exceptions +///////////////////////////////////////////////////////////////////////////// +struct parse_error: public graph_exception +{ + parse_error(const std::string& error) {statement = "parse error: " + error;} + virtual ~parse_error() throw() {} + virtual const char* what() const throw() {return statement.c_str();} + std::string statement; +}; + + +class mutate_graph +{ +public: + virtual ~mutate_graph() {} + virtual bool is_directed() const = 0; + + virtual boost::any do_add_vertex() = 0; + virtual std::pair do_add_edge(boost::any source, boost::any target) = 0; + + virtual void + set_graph_property(const std::string& name, const std::string& value, const std::string& value_type) = 0; + + virtual void + set_vertex_property(const std::string& name, boost::any vertex, const std::string& value, const std::string& value_type) = 0; + + virtual void + set_edge_property(const std::string& name, boost::any edge, const std::string& value, const std::string& value_type) = 0; +}; + +template +class mutate_graph_impl : public mutate_graph +{ + typedef typename graph_traits::vertex_descriptor vertex_descriptor; + typedef typename graph_traits::edge_descriptor edge_descriptor; + + public: + mutate_graph_impl(MutableGraph& g, dynamic_properties& dp) + : m_g(g), m_dp(dp) { } + + bool is_directed() const + { + return is_convertible::directed_category, + directed_tag>::value; + } + + virtual any do_add_vertex() + { + return any(add_vertex(m_g)); + } + + virtual std::pair do_add_edge(any source, any target) + { + std::pair retval = add_edge(any_cast(source), + any_cast(target), m_g); + return std::make_pair(any(retval.first), retval.second); + } + + virtual void + set_graph_property(const std::string& name, const std::string& value, const std::string& value_type) + { + bool type_found = false; + try + { + mpl::for_each(put_property + (name, m_dp, m_g, value, value_type, m_type_names, type_found)); + } + catch (bad_lexical_cast) + { + throw parse_error("invalid value \"" + value + "\" for key " + + name + " of type " + value_type); + } + if (!type_found) + throw parse_error("unrecognized type \"" + value_type + + "\" for key " + name); + + } + + virtual void + set_vertex_property(const std::string& name, any vertex, const std::string& value, const std::string& value_type) + { + bool type_found = false; + try + { + mpl::for_each(put_property + (name, m_dp, any_cast(vertex), + value, value_type, m_type_names, type_found)); + } + catch (bad_lexical_cast) + { + throw parse_error("invalid value \"" + value + "\" for key " + + name + " of type " + value_type); + } + if (!type_found) + throw parse_error("unrecognized type \"" + value_type + + "\" for key " + name); + + } + + virtual void + set_edge_property(const std::string& name, any edge, const std::string& value, const std::string& value_type) + { + bool type_found = false; + try + { + mpl::for_each(put_property + (name, m_dp, any_cast(edge), + value, value_type, m_type_names, type_found)); + } + catch (bad_lexical_cast) + { + throw parse_error("invalid value \"" + value + "\" for key " + + name + " of type " + value_type); + } + if (!type_found) + throw parse_error("unrecognized type \"" + value_type + + "\" for key " + name); + } + + template + class put_property + { + public: + put_property(const std::string& name, dynamic_properties& dp, const Key& key, + const std::string& value, const std::string& value_type, + char** type_names, bool& type_found) + : m_name(name), m_dp(dp), m_key(key), m_value(value), + m_value_type(value_type), m_type_names(type_names), + m_type_found(type_found) {} + template + void operator()(Value) + { + if (m_value_type == m_type_names[mpl::find::type::pos::value]) + { + put(m_name, m_dp, m_key, lexical_cast(m_value)); + m_type_found = true; + } + } + private: + const std::string& m_name; + dynamic_properties& m_dp; + const Key& m_key; + const std::string& m_value; + const std::string& m_value_type; + char** m_type_names; + bool& m_type_found; + }; + +protected: + MutableGraph& m_g; + dynamic_properties& m_dp; + typedef mpl::vector value_types; + static char* m_type_names[]; +}; + +template +char* mutate_graph_impl::m_type_names[] = {"boolean", "int", "long", "float", "double", "string"}; + +void +read_graphml(std::istream& in, mutate_graph& g); + +template +void +read_graphml(std::istream& in, MutableGraph& g, dynamic_properties& dp) +{ + mutate_graph_impl mg(g,dp); + read_graphml(in, mg); +} + +template +class get_type_name +{ +public: + get_type_name(const std::type_info& type, char** type_names, std::string& type_name) + : m_type(type), m_type_names(type_names), m_type_name(type_name) {} + template + void operator()(Type) + { + if (typeid(Type) == m_type) + m_type_name = m_type_names[mpl::find::type::pos::value]; + } +private: + const std::type_info &m_type; + char** m_type_names; + std::string &m_type_name; +}; + + +template +void +write_graphml(std::ostream& out, const Graph& g, VertexIndexMap vertex_index, + const dynamic_properties& dp, bool ordered_vertices=false) +{ + typedef typename graph_traits::directed_category directed_category; + typedef typename graph_traits::edge_descriptor edge_descriptor; + typedef typename graph_traits::vertex_descriptor vertex_descriptor; + + BOOST_STATIC_CONSTANT(bool, + graph_is_directed = + (is_convertible::value)); + + out << "\n" + << "\n"; + + typedef mpl::vector value_types; + char* type_names[] = {"boolean", "int", "int", "int", "int", "long", "long", "long", "long", "float", "double", "double", "string"}; + std::map graph_key_ids; + std::map vertex_key_ids; + std::map edge_key_ids; + int key_count = 0; + + // Output keys + for (dynamic_properties::const_iterator i = dp.begin(); i != dp.end(); ++i) + { + std::string key_id = "key" + lexical_cast(key_count++); + if (i->second->key() == typeid(Graph)) + vertex_key_ids[i->first] = key_id; + else if (i->second->key() == typeid(vertex_descriptor)) + vertex_key_ids[i->first] = key_id; + else if (i->second->key() == typeid(edge_descriptor)) + edge_key_ids[i->first] = key_id; + else + continue; + std::string type_name = "string"; + mpl::for_each(get_type_name(i->second->value(), type_names, type_name)); + out << " second->key() == typeid(Graph) ? "graph" : (i->second->key() == typeid(vertex_descriptor) ? "node" : "edge")) << "\"" + << " attr.name=\"" << i->first << "\"" + << " attr.type=\"" << type_name << "\"" + << " />\n"; + } + + out << " \n"; + + // Output graph data + for (dynamic_properties::const_iterator i = dp.begin(); i != dp.end(); ++i) + { + if (i->second->key() == typeid(Graph)) + { + out << " first] << "\">" + << i->second->get_string(g) << "\n"; + } + } + + typedef typename graph_traits::vertex_iterator vertex_iterator; + vertex_iterator v, v_end; + for (tie(v, v_end) = vertices(g); v != v_end; ++v) + { + out << " \n"; + // Output data + for (dynamic_properties::const_iterator i = dp.begin(); i != dp.end(); ++i) + { + if (i->second->key() == typeid(vertex_descriptor)) + { + out << " first] << "\">" + << i->second->get_string(*v) << "\n"; + } + } + out << " \n"; + } + + typedef typename graph_traits::edge_iterator edge_iterator; + edge_iterator e, e_end; + typename graph_traits::edges_size_type edge_count = 0; + for (tie(e, e_end) = edges(g); e != e_end; ++e) + { + out << " \n"; + + // Output data + for (dynamic_properties::const_iterator i = dp.begin(); i != dp.end(); ++i) + { + if (i->second->key() == typeid(edge_descriptor)) + { + out << " first] << "\">" + << i->second->get_string(*e) << "\n"; + } + } + out << " \n"; + } + + out << " \n" + << "\n"; +} + + +template +void +write_graphml(std::ostream& out, const Graph& g, const dynamic_properties& dp, + bool ordered_vertices=false) +{ + write_graphml(out, g, get(vertex_index, g), dp, ordered_vertices); +} + +} // boost namespace + +#endif // BOOST_GRAPH_GRAPHML_HPP diff --git a/src/graphml.cpp b/src/graphml.cpp new file mode 100644 index 00000000..01ddda11 --- /dev/null +++ b/src/graphml.cpp @@ -0,0 +1,347 @@ +// Copyright (C) 2006 Tiago de Paula Peixoto +// Copyright (C) 2004 The Trustees of Indiana University. +// +// Use, modification and distribution is subject to the Boost Software +// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// Authors: Douglas Gregor +// Andrew Lumsdaine +// Tiago de Paula Peixoto + +#include +#include +#include +#include + +using namespace boost; + +class graphml_reader +{ +public: + graphml_reader(mutate_graph& g) + : m_g(g), m_canonical_vertices(false) { } + + void run(std::istream& in) + { + const int buffer_size = 4096; + XML_Parser parser = XML_ParserCreateNS(0,'|'); + XML_SetElementHandler(parser, &on_start_element, &on_end_element); + XML_SetCharacterDataHandler(parser, &on_character_data); + XML_SetUserData(parser, this); + char buffer[buffer_size]; + do + { + in.read(buffer, buffer_size); + } + while (XML_Parse(parser, buffer, in.gcount(), in.gcount() == 0) && in.good()); + + if (in.good()) + { + std::stringstream s; + s << "Parse error: " << XML_ErrorString(XML_GetErrorCode(parser)) + << " on line " << XML_GetCurrentLineNumber(parser) + <<", column " << XML_GetCurrentColumnNumber(parser); + XML_ParserFree(parser); + throw parse_error(s.str()); + } + XML_ParserFree(parser); + } + +private: + /// The kinds of keys. Not all of these are supported + enum key_kind { + graph_key, + node_key, + edge_key, + hyperedge_key, + port_key, + endpoint_key, + all_key + }; + + static void + on_start_element(void* user_data, const XML_Char *c_name, + const XML_Char **atts) + { + graphml_reader* self = static_cast(user_data); + + std::string name(c_name); + replace_first(name, "http://graphml.graphdrawing.org/xmlns|", ""); + + if (name == "key") + { + std::string id; + std::string key_name; + std::string key_type; + key_kind kind = all_key; + + while (*atts) + { + std::string name = *atts++; + std::string value = *atts++; + + if (name == "id") id = value; + else if (name == "attr.name") key_name = value; + else if (name == "attr.type") key_type = value; + else if (name == "for") + { + if (value == "graph") kind = graph_key; + else if (value == "node") kind = node_key; + else if (value == "edge") kind = edge_key; + else if (value == "hyperedge") kind = hyperedge_key; + else if (value == "port") kind = port_key; + else if (value == "endpoint") kind = endpoint_key; + else if (value == "all") kind = all_key; + else + { + throw parse_error("unrecognized key kind '" + value + "'"); + } + } + } + + self->m_keys[id] = kind; + self->m_key_name[id] = key_name; + self->m_key_type[id] = key_type; + self->m_active_key = id; + } + else if (name == "node") + { + std::string id; + + while (*atts) + { + std::string name = *atts++; + std::string value = *atts++; + + if (name == "id") id = value; + } + + self->handle_vertex(id); + self->m_active_descriptor = id; + } + else if (name == "edge") + { + std::string id; + std::string source, target; + while (*atts) + { + std::string name = *atts++; + std::string value = *atts++; + + if (name == "id") id = value; + else if (name == "source") source = value; + else if (name == "target") target = value; + else if (name == "directed") + { + bool edge_is_directed = (value == "directed"); + if (edge_is_directed != self->m_g.is_directed()) + { + if (edge_is_directed) + throw directed_graph_error(); + else + throw undirected_graph_error(); + } + } + } + + self->m_active_descriptor = self->m_edge.size(); + self->handle_edge(source, target); + } + else if (name == "graph") + { + while (*atts) + { + std::string name = *atts++; + std::string value = *atts++; + + if (name == "edgedefault") + { + bool edge_is_directed = (value == "directed"); + if (edge_is_directed != self->m_g.is_directed()) + { + if (edge_is_directed) + throw directed_graph_error(); + else + throw undirected_graph_error(); + } + } + else if (name == "parse.nodeids") + { + self->m_canonical_vertices = (value == "canonical"); + } + } + self->m_active_descriptor = ""; + } + else if (name == "data") + { + while (*atts) + { + std::string name = *atts++; + std::string value = *atts++; + + if (name == "key") self->m_active_key = value; + } + } + + self->m_character_data.clear(); + } + + static void + on_end_element(void* user_data, const XML_Char *c_name) + { + graphml_reader* self = static_cast(user_data); + + std::string name(c_name); + replace_first(name, "http://graphml.graphdrawing.org/xmlns|", ""); + + if (name == "data") + { + self->handle_property(self->m_active_key, self->m_active_descriptor, + self->m_character_data); + } + else if (name == "default") + { + self->m_key_default[self->m_active_key] = self->m_character_data; + } + } + + static void + on_character_data(void* user_data, const XML_Char* s, int len) + { + graphml_reader* self = static_cast(user_data); + self->m_character_data.append(s, len); + } + + void + handle_vertex(const std::string& v) + { + bool is_new = false; + + if (m_canonical_vertices) + { + size_t id; + + //strip leading "n" from name + try + { + id = lexical_cast(std::string(v,1)); + } + catch (bad_lexical_cast) + { + throw parse_error("invalid vertex: " + v); + } + + while(id >= m_canonical_vertex.size()) + { + m_canonical_vertex.push_back(m_g.do_add_vertex()); + is_new = true; + } + } + else + { + if (m_vertex.find(v) == m_vertex.end()) + { + m_vertex[v] = m_g.do_add_vertex(); + is_new = true; + } + } + + if (is_new) + { + std::map::iterator iter; + for (iter = m_key_default.begin(); iter != m_key_default.end(); ++iter) + { + if (m_keys[iter->first] == node_key) + handle_property(iter->first, v, iter->second); + } + } + } + + any + get_vertex_descriptor(const std::string& v) + { + if (m_canonical_vertices) + { + //strip leading "n" from name + size_t id = lexical_cast(std::string(v,1)); + return m_canonical_vertex[id]; + } + else + { + return m_vertex[v]; + } + } + + void + handle_edge(const std::string& u, const std::string& v) + { + handle_vertex(u); + handle_vertex(v); + + any source, target; + source = get_vertex_descriptor(u); + target = get_vertex_descriptor(v); + + any edge; + bool added; + tie(edge, added) = m_g.do_add_edge(source, target); + if (!added) + throw bad_parallel_edge(u, v); + + size_t e = m_edge.size(); + m_edge.push_back(edge); + + std::map::iterator iter; + for (iter = m_key_default.begin(); iter != m_key_default.end(); ++iter) + { + if (m_keys[iter->first] == edge_key) + handle_property(iter->first, e, iter->second); + } + } + + void handle_property(const std::string& key_id, const variant& descriptor, const std::string& value) + { + if (get(&descriptor)) + { + if (get(descriptor) == "") + m_g.set_graph_property(m_key_name[key_id], value, m_key_type[key_id]); + else + m_g.set_vertex_property(m_key_name[key_id], get_vertex_descriptor(get(descriptor)), value, m_key_type[key_id]); + } + else + { + m_g.set_edge_property(m_key_name[key_id], get_edge_descriptor(get(descriptor)), value, m_key_type[key_id]); + } + } + + any + get_edge_descriptor(size_t e) + { + return m_edge[e]; + } + + mutate_graph& m_g; + std::map m_keys; + std::map m_key_name; + std::map m_key_type; + std::map m_key_default; + std::map m_vertex; + std::vector m_canonical_vertex; + std::vector m_edge; + variant m_active_descriptor; + std::string m_active_key; + std::string m_character_data; + bool m_canonical_vertices; + bool m_canonical_edges; +}; + +namespace boost +{ +void +read_graphml(std::istream& in, mutate_graph& g) +{ + graphml_reader reader(g); + reader.run(in); +} +} diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index df2f7639..c0452581 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -3,6 +3,13 @@ import modules ; +local optional_tests ; + +if [ modules.peek : EXPAT_INCLUDE ] && [ modules.peek : EXPAT_LIBPATH ] +{ + optional_tests += [ run graphml_test.cpp ../build//boost_graph ] ; +} + test-suite graph_test : [ run transitive_closure_test.cpp ] @@ -50,7 +57,6 @@ test-suite graph_test : [ compile graph_concepts.cpp ] - [ run graphviz_test.cpp /boost/test//boost_test_exec_monitor/static ../build//boost_graph ] @@ -96,6 +102,8 @@ test-suite graph_test : [ run max_flow_test.cpp ] [ run kolmogorov_max_flow_test.cpp ] + + $(optional_tests) ; # Run SDB tests only when -sSDB= is set. diff --git a/test/graphml_test.cpp b/test/graphml_test.cpp new file mode 100644 index 00000000..10742896 --- /dev/null +++ b/test/graphml_test.cpp @@ -0,0 +1,81 @@ +// Copyright (C) 2006 Tiago de Paula Peixoto +// +// Boost Software License - Version 1.0 - August 17th, 2003 +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +// Authors: Tiago de Paula Peixoto + +#include +#include +#include + +using namespace std; +using namespace boost; + +int main(int argc, char** argv) +{ + typedef adjacency_list, + property > graph_t; + graph_t g; + dynamic_properties dp; + dp.property("foo",get(vertex_color_t(),g)); + dp.property("weight",get(edge_weight_t(),g)); + + ifstream ifile("graphml_test.xml"); + read_graphml(ifile, g, dp); + ifile.close(); + + assert(num_vertices(g) == 9); + assert(num_edges(g) == 9); + assert(get(vertex_color_t(), g, vertex(2,g)) == 100); + assert(get(vertex_color_t(), g, vertex(3,g)) == 42); + assert(get(edge_weight_t(), g, edge(vertex(0,g),vertex(1,g),g).first) == 0.0); + assert(get(edge_weight_t(), g, edge(vertex(1,g),vertex(2,g),g).first) == 0.8); + + ofstream ofile("graphml_test_out.xml"); + write_graphml(ofile, g, dp); + ofile.close(); + + graph_t g2; + dynamic_properties dp2; + dp2.property("foo",get(vertex_color_t(),g2)); + dp2.property("weight",get(edge_weight_t(),g2)); + ifile.open("graphml_test_out.xml"); + read_graphml(ifile, g2, dp2); + ifile.close(); + + assert(num_vertices(g) == num_vertices(g2)); + assert(num_edges(g) == num_edges(g2)); + + graph_traits::vertex_iterator v, v_end; + for (tie(v,v_end) = vertices(g); v != v_end; ++v) + assert(get(vertex_color_t(), g, *v) == get(vertex_color_t(), g2, *v)); + + graph_traits::edge_iterator e, e_end; + for (tie(e,e_end) = edges(g); e != e_end; ++e) + assert(get(edge_weight_t(), g, *e) == get(edge_weight_t(), g2, *e)); + + return 0; +} diff --git a/test/graphml_test.xml b/test/graphml_test.xml new file mode 100644 index 00000000..63d9899e --- /dev/null +++ b/test/graphml_test.xml @@ -0,0 +1,52 @@ + + + + 42 + + + 0.0 + + + + + + + 100 + + + + + + 0 + + + + 0.8 + + + 0.5 + + + 0.2 + + + 10.1 + + + 5.5 + + + 2.0 + + + 0.1 + + + + + + 10.0 + + + + diff --git a/test/transitive_closure_test.cpp b/test/transitive_closure_test.cpp index 791ff9ee..f951a2ed 100644 --- a/test/transitive_closure_test.cpp +++ b/test/transitive_closure_test.cpp @@ -5,6 +5,7 @@ // http://www.boost.org/LICENSE_1_0.txt) +#include #include #include