| // Copyright 2004-9 Trustees of Indiana University |
| |
| // Distributed under 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) |
| |
| // |
| // read_graphviz_new.cpp - |
| // Initialize a model of the BGL's MutableGraph concept and an associated |
| // collection of property maps using a graph expressed in the GraphViz |
| // DOT Language. |
| // |
| // Based on the grammar found at: |
| // http://www.graphviz.org/cvs/doc/info/lang.html |
| // |
| // Jeremiah rewrite used grammar found at: |
| // http://www.graphviz.org/doc/info/lang.html |
| // and page 34 or http://www.graphviz.org/pdf/dotguide.pdf |
| // |
| // See documentation for this code at: |
| // http://www.boost.org/libs/graph/doc/read-graphviz.html |
| // |
| |
| // Author: Jeremiah Willcock |
| // Ronald Garcia |
| // |
| |
| #define BOOST_GRAPH_SOURCE |
| #include <boost/ref.hpp> |
| #include <boost/function/function2.hpp> |
| #include <boost/property_map/dynamic_property_map.hpp> |
| #include <boost/graph/graph_traits.hpp> |
| #include <boost/detail/workaround.hpp> |
| #include <boost/algorithm/string/case_conv.hpp> |
| #include <algorithm> |
| #include <exception> // for std::exception |
| #include <string> |
| #include <vector> |
| #include <set> |
| #include <utility> |
| #include <map> |
| #include <iostream> |
| #include <cstdlib> |
| #include <boost/throw_exception.hpp> |
| #include <boost/regex.hpp> |
| #include <boost/function.hpp> |
| #include <boost/bind.hpp> |
| #include <boost/graph/dll_import_export.hpp> |
| #include <boost/graph/graphviz.hpp> |
| |
| namespace boost { |
| |
| namespace read_graphviz_detail { |
| struct token { |
| enum token_type { |
| kw_strict, |
| kw_graph, |
| kw_digraph, |
| kw_node, |
| kw_edge, |
| kw_subgraph, |
| left_brace, |
| right_brace, |
| semicolon, |
| equal, |
| left_bracket, |
| right_bracket, |
| comma, |
| colon, |
| dash_greater, |
| dash_dash, |
| plus, |
| left_paren, |
| right_paren, |
| at, |
| identifier, |
| quoted_string, // Only used internally in tokenizer |
| eof, |
| invalid |
| }; |
| token_type type; |
| std::string normalized_value; // May have double-quotes removed and/or some escapes replaced |
| token(token_type type, const std::string& normalized_value) |
| : type(type), normalized_value(normalized_value) {} |
| token(): type(invalid), normalized_value("") {} |
| friend std::ostream& operator<<(std::ostream& o, const token& t) { |
| switch (t.type) { |
| case token::kw_strict: o << "<strict>"; break; |
| case token::kw_graph: o << "<graph>"; break; |
| case token::kw_digraph: o << "<digraph>"; break; |
| case token::kw_node: o << "<node>"; break; |
| case token::kw_edge: o << "<edge>"; break; |
| case token::kw_subgraph: o << "<subgraph>"; break; |
| case token::left_brace: o << "<left_brace>"; break; |
| case token::right_brace: o << "<right_brace>"; break; |
| case token::semicolon: o << "<semicolon>"; break; |
| case token::equal: o << "<equal>"; break; |
| case token::left_bracket: o << "<left_bracket>"; break; |
| case token::right_bracket: o << "<right_bracket>"; break; |
| case token::comma: o << "<comma>"; break; |
| case token::colon: o << "<colon>"; break; |
| case token::dash_greater: o << "<dash-greater>"; break; |
| case token::dash_dash: o << "<dash-dash>"; break; |
| case token::plus: o << "<plus>"; break; |
| case token::left_paren: o << "<left_paren>"; break; |
| case token::right_paren: o << "<right_paren>"; break; |
| case token::at: o << "<at>"; break; |
| case token::identifier: o << "<identifier>"; break; |
| case token::quoted_string: o << "<quoted_string>"; break; |
| case token::eof: o << "<eof>"; break; |
| default: o << "<invalid type>"; break; |
| } |
| o << " '" << t.normalized_value << "'"; |
| return o; |
| } |
| }; |
| |
| bad_graphviz_syntax lex_error(const std::string& errmsg, char bad_char) { |
| if (bad_char == '\0') { |
| return bad_graphviz_syntax(errmsg + " (at end of input)"); |
| } else { |
| return bad_graphviz_syntax(errmsg + " (char is '" + bad_char + "')"); |
| } |
| } |
| |
| bad_graphviz_syntax parse_error(const std::string& errmsg, const token& bad_token) { |
| return bad_graphviz_syntax(errmsg + " (token is \"" + boost::lexical_cast<std::string>(bad_token) + "\")"); |
| } |
| |
| struct tokenizer { |
| std::string::const_iterator begin, end; |
| std::vector<token> lookahead; |
| // Precomputed regexes |
| boost::regex stuff_to_skip; |
| boost::regex basic_id_token; |
| boost::regex punctuation_token; |
| boost::regex number_token; |
| boost::regex quoted_string_token; |
| boost::regex xml_tag_token; |
| boost::regex cdata; |
| |
| tokenizer(const std::string& str) : begin(str.begin()), end(str.end()) |
| { |
| std::string end_of_token = "(?=(?:\\W))"; |
| std::string whitespace = "(?:\\s+)"; |
| std::string slash_slash_comment = "(?://.*$)"; |
| std::string slash_star_comment = "(?:/\\*.*?\\*/)"; |
| std::string hash_comment = "(?:^#.*?$)"; |
| std::string backslash_newline = "(?:[\\\\][\\n])"; |
| stuff_to_skip = "\\A(?:" + whitespace + "|" + |
| slash_slash_comment + "|" + |
| slash_star_comment + "|" + |
| hash_comment + "|" + |
| backslash_newline + ")*"; |
| basic_id_token = "\\A([[:alpha:]_](?:\\w*))"; |
| punctuation_token = "\\A([][{};=,:+()@]|[-][>-])"; |
| number_token = "\\A([-]?(?:(?:\\.\\d+)|(?:\\d+(?:\\.\\d*)?)))"; |
| quoted_string_token = "\\A(\"(?:[^\"\\\\]|(?:[\\\\].))*\")"; |
| xml_tag_token = "\\A<(/?)(?:[^!?'\"]|(?:'[^']*?')|(?:\"[^\"]*?\"))*?(/?)>"; |
| cdata = "\\A\\Q<![CDATA[\\E.*?\\Q]]>\\E"; |
| } |
| |
| void skip() { |
| boost::match_results<std::string::const_iterator> results; |
| #ifndef NDEBUG |
| bool found = |
| #endif |
| boost::regex_search(begin, end, results, stuff_to_skip); |
| #ifndef NDEBUG |
| assert (found); |
| #endif |
| boost::sub_match<std::string::const_iterator> sm1 = results.suffix(); |
| assert (sm1.second == end); |
| begin = sm1.first; |
| } |
| |
| token get_token_raw() { |
| if (!lookahead.empty()) { |
| token t = lookahead.front(); |
| lookahead.erase(lookahead.begin()); |
| return t; |
| } |
| skip(); |
| if (begin == end) return token(token::eof, ""); |
| // Look for keywords first |
| bool found; |
| boost::match_results<std::string::const_iterator> results; |
| found = boost::regex_search(begin, end, results, basic_id_token); |
| if (found) { |
| std::string str = results[1].str(); |
| std::string str_lower = boost::algorithm::to_lower_copy(str); |
| begin = results.suffix().first; |
| if (str_lower == "strict") { |
| return token(token::kw_strict, str); |
| } else if (str_lower == "graph") { |
| return token(token::kw_graph, str); |
| } else if (str_lower == "digraph") { |
| return token(token::kw_digraph, str); |
| } else if (str_lower == "node") { |
| return token(token::kw_node, str); |
| } else if (str_lower == "edge") { |
| return token(token::kw_edge, str); |
| } else if (str_lower == "subgraph") { |
| return token(token::kw_subgraph, str); |
| } else { |
| return token(token::identifier, str); |
| } |
| } |
| found = boost::regex_search(begin, end, results, punctuation_token); |
| if (found) { |
| std::string str = results[1].str(); |
| begin = results.suffix().first; |
| switch (str[0]) { |
| case '[': return token(token::left_bracket, str); |
| case ']': return token(token::right_bracket, str); |
| case '{': return token(token::left_brace, str); |
| case '}': return token(token::right_brace, str); |
| case ';': return token(token::semicolon, str); |
| case '=': return token(token::equal, str); |
| case ',': return token(token::comma, str); |
| case ':': return token(token::colon, str); |
| case '+': return token(token::plus, str); |
| case '(': return token(token::left_paren, str); |
| case ')': return token(token::right_paren, str); |
| case '@': return token(token::at, str); |
| case '-': { |
| switch (str[1]) { |
| case '-': return token(token::dash_dash, str); |
| case '>': return token(token::dash_greater, str); |
| default: assert (!"Definition of punctuation_token does not match switch statement"); |
| } |
| } |
| default: assert (!"Definition of punctuation_token does not match switch statement"); |
| } |
| } |
| found = boost::regex_search(begin, end, results, number_token); |
| if (found) { |
| std::string str = results[1].str(); |
| begin = results.suffix().first; |
| return token(token::identifier, str); |
| } |
| found = boost::regex_search(begin, end, results, quoted_string_token); |
| if (found) { |
| std::string str = results[1].str(); |
| begin = results.suffix().first; |
| // Remove the beginning and ending quotes |
| assert (str.size() >= 2); |
| str.erase(str.begin()); |
| str.erase(str.end() - 1); |
| // Unescape quotes in the middle, but nothing else (see format spec) |
| for (size_t i = 0; i + 1 < str.size() /* May change */; ++i) { |
| if (str[i] == '\\' && str[i + 1] == '"') { |
| str.erase(str.begin() + i); |
| // Don't need to adjust i |
| } else if (str[i] == '\\' && str[i + 1] == '\n') { |
| str.erase(str.begin() + i); |
| str.erase(str.begin() + i); |
| --i; // Invert ++ that will be applied |
| } |
| } |
| return token(token::quoted_string, str); |
| } |
| if (*begin == '<') { |
| std::string::const_iterator saved_begin = begin; |
| int counter = 0; |
| do { |
| if (begin == end) throw_lex_error("Unclosed HTML string"); |
| if (*begin != '<') { |
| ++begin; |
| continue; |
| } |
| found = boost::regex_search(begin, end, results, xml_tag_token); |
| if (found) { |
| begin = results.suffix().first; |
| if (results[1].str() == "/") { // Close tag |
| --counter; |
| } else if (results[2].str() == "/") { // Empty tag |
| } else { // Open tag |
| ++counter; |
| } |
| continue; |
| } |
| found = boost::regex_search(begin, end, results, cdata); |
| if (found) { |
| begin = results.suffix().first; |
| continue; |
| } |
| throw_lex_error("Invalid contents in HTML string"); |
| } while (counter > 0); |
| return token(token::identifier, std::string(saved_begin, begin)); |
| } else { |
| throw_lex_error("Invalid character"); |
| return token(); |
| } |
| } |
| |
| token peek_token_raw() { |
| if (lookahead.empty()) { |
| token t = get_token_raw(); |
| lookahead.push_back(t); |
| } |
| return lookahead.front(); |
| } |
| |
| token get_token() { // Handle string concatenation |
| token t = get_token_raw(); |
| if (t.type != token::quoted_string) return t; |
| std::string str = t.normalized_value; |
| while (peek_token_raw().type == token::plus) { |
| get_token_raw(); |
| token t2 = get_token_raw(); |
| if (t2.type != token::quoted_string) { |
| throw_lex_error("Must have quoted string after string concatenation"); |
| } |
| str += t2.normalized_value; |
| } |
| return token(token::identifier, str); // Note that quoted_string does not get passed to the parser |
| } |
| |
| void throw_lex_error(const std::string& errmsg) { |
| boost::throw_exception(lex_error(errmsg, (begin == end ? '\0' : *begin))); |
| } |
| }; |
| |
| struct edge_endpoint { |
| bool is_subgraph; |
| node_and_port node_ep; |
| subgraph_name subgraph_ep; |
| |
| static edge_endpoint node(const node_and_port& ep) { |
| edge_endpoint r; |
| r.is_subgraph = false; |
| r.node_ep = ep; |
| return r; |
| } |
| |
| static edge_endpoint subgraph(const subgraph_name& ep) { |
| edge_endpoint r; |
| r.is_subgraph = true; |
| r.subgraph_ep = ep; |
| return r; |
| } |
| }; |
| |
| struct node_or_subgraph_ref { |
| bool is_subgraph; |
| std::string name; // Name for subgraphs or nodes, "___root___" for root graph |
| }; |
| |
| static node_or_subgraph_ref noderef(const node_name& n) { |
| node_or_subgraph_ref r; |
| r.is_subgraph = false; |
| r.name = n; |
| return r; |
| } |
| |
| static node_or_subgraph_ref subgraphref(const subgraph_name& n) { |
| node_or_subgraph_ref r; |
| r.is_subgraph = true; |
| r.name = n; |
| return r; |
| } |
| |
| typedef std::vector<node_or_subgraph_ref> subgraph_member_list; |
| |
| struct subgraph_info { |
| properties def_node_props; |
| properties def_edge_props; |
| subgraph_member_list members; |
| }; |
| |
| struct parser { |
| tokenizer the_tokenizer; |
| std::vector<token> lookahead; |
| parser_result& r; |
| std::map<subgraph_name, subgraph_info> subgraphs; |
| std::string current_subgraph_name; |
| int sgcounter; // Counter for anonymous subgraphs |
| std::set<std::pair<node_name, node_name> > existing_edges; // Used for checking in strict graphs |
| |
| subgraph_info& current() {return subgraphs[current_subgraph_name];} |
| properties& current_graph_props() {return r.graph_props[current_subgraph_name];} |
| subgraph_member_list& current_members() {return current().members;} |
| |
| parser(const std::string& gr, parser_result& result) |
| : the_tokenizer(gr), lookahead(), r(result), sgcounter(0) { |
| current_subgraph_name = "___root___"; |
| current() = subgraph_info(); // Initialize root graph |
| current_graph_props().clear(); |
| current_members().clear(); |
| } |
| |
| token get() { |
| if (lookahead.empty()) { |
| token t = the_tokenizer.get_token(); |
| return t; |
| } else { |
| token t = lookahead.front(); |
| lookahead.erase(lookahead.begin()); |
| return t; |
| } |
| } |
| |
| token peek() { |
| if (lookahead.empty()) { |
| lookahead.push_back(the_tokenizer.get_token()); |
| } |
| return lookahead.front(); |
| } |
| |
| void error(const std::string& str) { |
| boost::throw_exception(parse_error(str, peek())); |
| } |
| |
| void parse_graph(bool want_directed) { |
| bool is_strict = false; |
| bool is_directed = false; |
| std::string name; |
| if (peek().type == token::kw_strict) {get(); is_strict = true;} |
| switch (peek().type) { |
| case token::kw_graph: is_directed = false; break; |
| case token::kw_digraph: is_directed = true; break; |
| default: error("Wanted \"graph\" or \"digraph\""); |
| } |
| r.graph_is_directed = is_directed; // Used to check edges |
| r.graph_is_strict = is_strict; |
| if (want_directed != r.graph_is_directed) { |
| if (want_directed) { |
| boost::throw_exception(boost::undirected_graph_error()); |
| } else { |
| boost::throw_exception(boost::directed_graph_error()); |
| } |
| } |
| get(); |
| switch (peek().type) { |
| case token::identifier: name = peek().normalized_value; get(); break; |
| case token::left_brace: break; |
| default: error("Wanted a graph name or left brace"); |
| } |
| if (peek().type == token::left_brace) get(); else error("Wanted a left brace to start the graph"); |
| parse_stmt_list(); |
| if (peek().type == token::right_brace) get(); else error("Wanted a right brace to end the graph"); |
| if (peek().type == token::eof) {} else error("Wanted end of file"); |
| } |
| |
| void parse_stmt_list() { |
| while (true) { |
| if (peek().type == token::right_brace) return; |
| parse_stmt(); |
| if (peek().type == token::semicolon) get(); |
| } |
| } |
| |
| void parse_stmt() { |
| switch (peek().type) { |
| case token::kw_node: |
| case token::kw_edge: |
| case token::kw_graph: parse_attr_stmt(); break; |
| case token::kw_subgraph: |
| case token::left_brace: |
| case token::identifier: { |
| token id = get(); |
| if (id.type == token::identifier && peek().type == token::equal) { // Graph property |
| get(); |
| if (peek().type != token::identifier) error("Wanted identifier as right side of ="); |
| token id2 = get(); |
| current_graph_props()[id.normalized_value] = id2.normalized_value; |
| } else { |
| edge_endpoint ep = parse_endpoint_rest(id); |
| if (peek().type == token::dash_dash || peek().type == token::dash_greater) { // Edge |
| parse_edge_stmt(ep); |
| } else { |
| if (!ep.is_subgraph) { // Only nodes can have attribute lists |
| // This node already exists because of its first mention |
| // (properties set to defaults by parse_node_and_port, called |
| // by parse_endpoint_rest) |
| properties this_node_props; |
| if (peek().type == token::left_bracket) { |
| parse_attr_list(this_node_props); |
| } |
| for (properties::const_iterator i = this_node_props.begin(); |
| i != this_node_props.end(); ++i) { |
| // Override old properties with same names |
| r.nodes[ep.node_ep.name][i->first] = i->second; |
| } |
| current_members().push_back(noderef(ep.node_ep.name)); |
| } else { |
| current_members().push_back(subgraphref(ep.subgraph_ep)); |
| } |
| } |
| } |
| break; |
| } |
| default: error("Invalid start token for statement"); |
| } |
| } |
| |
| void parse_attr_stmt() { |
| switch (get().type) { |
| case token::kw_graph: parse_attr_list(current_graph_props()); break; |
| case token::kw_node: parse_attr_list(current().def_node_props); break; |
| case token::kw_edge: parse_attr_list(current().def_edge_props); break; |
| default: assert (!"Bad attr_stmt case"); |
| } |
| } |
| |
| edge_endpoint parse_endpoint() { |
| switch (peek().type) { |
| case token::kw_subgraph: |
| case token::left_brace: |
| case token::identifier: { |
| token first = get(); |
| return parse_endpoint_rest(first); |
| } |
| default: { |
| error("Wanted \"subgraph\", \"{\", or identifier to start node or subgraph"); |
| return edge_endpoint(); |
| } |
| } |
| } |
| |
| edge_endpoint parse_endpoint_rest(const token& first_token) { |
| switch (first_token.type) { |
| case token::kw_subgraph: |
| case token::left_brace: return edge_endpoint::subgraph(parse_subgraph(first_token)); |
| default: return edge_endpoint::node(parse_node_and_port(first_token)); |
| } |
| } |
| |
| subgraph_name parse_subgraph(const token& first_token) { |
| std::string name; |
| bool is_anonymous = true; |
| if (first_token.type == token::kw_subgraph) { |
| if (peek().type == token::identifier) { |
| name = get().normalized_value; |
| is_anonymous = false; |
| } |
| } |
| if (is_anonymous) { |
| name = "___subgraph_" + |
| boost::lexical_cast<std::string>(++sgcounter); |
| } |
| if (subgraphs.find(name) == subgraphs.end()) { |
| subgraphs[name] = current(); // Initialize properties and defaults |
| subgraphs[name].members.clear(); // Except member list |
| } |
| if (first_token.type == token::kw_subgraph && peek().type != token::left_brace) { |
| if (is_anonymous) error("Subgraph reference needs a name"); |
| return name; |
| } |
| subgraph_name old_sg = current_subgraph_name; |
| current_subgraph_name = name; |
| if (peek().type == token::left_brace) get(); else error("Wanted left brace to start subgraph"); |
| parse_stmt_list(); |
| if (peek().type == token::right_brace) get(); else error("Wanted right brace to end subgraph"); |
| current_subgraph_name = old_sg; |
| return name; |
| } |
| |
| node_and_port parse_node_and_port(const token& name) { |
| // A node ID is a node name, followed optionally by a port angle and a |
| // port location (in either order); a port location is either :id, |
| // :id:id, or :(id,id); the last two forms are treated as equivalent |
| // although I am not sure about that. |
| node_and_port id; |
| id.name = name.normalized_value; |
| parse_more: |
| switch (peek().type) { |
| case token::at: { |
| get(); |
| if (peek().type != token::identifier) error("Wanted identifier as port angle"); |
| if (id.angle != "") error("Duplicate port angle"); |
| id.angle = get().normalized_value; |
| goto parse_more; |
| } |
| case token::colon: { |
| get(); |
| if (!id.location.empty()) error("Duplicate port location"); |
| switch (peek().type) { |
| case token::identifier: { |
| id.location.push_back(get().normalized_value); |
| switch (peek().type) { |
| case token::colon: { |
| get(); |
| if (peek().type != token::identifier) error("Wanted identifier as port location"); |
| id.location.push_back(get().normalized_value); |
| goto parse_more; |
| } |
| default: goto parse_more; |
| } |
| } |
| case token::left_paren: { |
| get(); |
| if (peek().type != token::identifier) error("Wanted identifier as first element of port location"); |
| id.location.push_back(get().normalized_value); |
| if (peek().type != token::comma) error("Wanted comma between parts of port location"); |
| get(); |
| if (peek().type != token::identifier) error("Wanted identifier as second element of port location"); |
| id.location.push_back(get().normalized_value); |
| if (peek().type != token::right_paren) error("Wanted right parenthesis to close port location"); |
| get(); |
| goto parse_more; |
| } |
| default: error("Wanted identifier or left parenthesis as start of port location"); |
| } |
| } |
| default: break; |
| } |
| if (r.nodes.find(id.name) == r.nodes.end()) { // First mention |
| r.nodes[id.name] = current().def_node_props; |
| } |
| return id; |
| } |
| |
| void parse_edge_stmt(const edge_endpoint& lhs) { |
| std::vector<edge_endpoint> nodes_in_chain(1, lhs); |
| while (true) { |
| bool leave_loop = true; |
| switch (peek().type) { |
| case token::dash_dash: { |
| if (r.graph_is_directed) error("Using -- in directed graph"); |
| get(); |
| nodes_in_chain.push_back(parse_endpoint()); |
| leave_loop = false; |
| break; |
| } |
| case token::dash_greater: { |
| if (!r.graph_is_directed) error("Using -> in undirected graph"); |
| get(); |
| nodes_in_chain.push_back(parse_endpoint()); |
| leave_loop = false; |
| break; |
| } |
| default: leave_loop = true; break; |
| } |
| if (leave_loop) break; |
| } |
| properties this_edge_props = current().def_edge_props; |
| if (peek().type == token::left_bracket) parse_attr_list(this_edge_props); |
| assert (nodes_in_chain.size() >= 2); // Should be in node parser otherwise |
| for (size_t i = 0; i + 1 < nodes_in_chain.size(); ++i) { |
| do_orig_edge(nodes_in_chain[i], nodes_in_chain[i + 1], this_edge_props); |
| } |
| } |
| |
| // Do an edge from the file, the edge may need to be expanded if it connects to a subgraph |
| void do_orig_edge(const edge_endpoint& src, const edge_endpoint& tgt, const properties& props) { |
| std::set<node_and_port> sources = get_recursive_members(src); |
| std::set<node_and_port> targets = get_recursive_members(tgt); |
| for (std::set<node_and_port>::const_iterator i = sources.begin(); i != sources.end(); ++i) { |
| for (std::set<node_and_port>::const_iterator j = targets.begin(); j != targets.end(); ++j) { |
| do_edge(*i, *j, props); |
| } |
| } |
| } |
| |
| // Get nodes in an edge_endpoint, recursively |
| std::set<node_and_port> get_recursive_members(const edge_endpoint& orig_ep) { |
| std::set<node_and_port> result; |
| std::vector<edge_endpoint> worklist(1, orig_ep); |
| std::set<subgraph_name> done; |
| while (!worklist.empty()) { |
| edge_endpoint ep = worklist.back(); |
| worklist.pop_back(); |
| if (ep.is_subgraph) { |
| if (done.find(ep.subgraph_ep) == done.end()) { |
| done.insert(ep.subgraph_ep); |
| std::map<subgraph_name, subgraph_info>::const_iterator |
| info_i = subgraphs.find(ep.subgraph_ep); |
| if (info_i != subgraphs.end()) { |
| const subgraph_member_list& members = info_i->second.members; |
| for (subgraph_member_list::const_iterator i = members.begin(); |
| i != members.end(); ++i) { |
| node_or_subgraph_ref ref = *i; |
| if (ref.is_subgraph) { |
| worklist.push_back(edge_endpoint::subgraph(ref.name)); |
| } else { |
| node_and_port np; |
| np.name = ref.name; |
| worklist.push_back(edge_endpoint::node(np)); |
| } |
| } |
| } |
| } |
| } else { |
| result.insert(ep.node_ep); |
| } |
| } |
| return result; |
| } |
| |
| // Do a fixed-up edge, with only nodes as endpoints |
| void do_edge(const node_and_port& src, const node_and_port& tgt, const properties& props) { |
| if (r.graph_is_strict) { |
| if (src.name == tgt.name) return; |
| std::pair<node_name, node_name> tag(src.name, tgt.name); |
| if (existing_edges.find(tag) != existing_edges.end()) { |
| return; // Parallel edge |
| } |
| existing_edges.insert(tag); |
| } |
| edge_info e; |
| e.source = src; |
| e.target = tgt; |
| e.props = props; |
| r.edges.push_back(e); |
| } |
| |
| void parse_attr_list(properties& props) { |
| while (true) { |
| if (peek().type == token::left_bracket) get(); else error("Wanted left bracket to start attribute list"); |
| while (true) { |
| switch (peek().type) { |
| case token::right_bracket: break; |
| case token::identifier: { |
| std::string lhs = get().normalized_value; |
| std::string rhs = "true"; |
| if (peek().type == token::equal) { |
| get(); |
| if (peek().type != token::identifier) error("Wanted identifier as value of attributed"); |
| rhs = get().normalized_value; |
| } |
| props[lhs] = rhs; |
| break; |
| } |
| default: error("Wanted identifier as name of attribute"); |
| } |
| if (peek().type == token::comma) {get(); continue;} |
| break; |
| } |
| if (peek().type == token::right_bracket) get(); else error("Wanted right bracket to end attribute list"); |
| if (peek().type != token::left_bracket) break; |
| } |
| } |
| }; |
| |
| void parse_graphviz_from_string(const std::string& str, parser_result& result, bool want_directed) { |
| parser p(str, result); |
| p.parse_graph(want_directed); |
| } |
| |
| // Some debugging stuff |
| std::ostream& operator<<(std::ostream& o, const node_and_port& n) { |
| o << n.name; |
| for (size_t i = 0; i < n.location.size(); ++i) { |
| o << ":" << n.location[i]; |
| } |
| if (!n.angle.empty()) o << "@" << n.angle; |
| return o; |
| } |
| |
| // Can't be operator<< because properties is just an std::map |
| std::string props_to_string(const properties& props) { |
| std::string result = "["; |
| for (properties::const_iterator i = props.begin(); i != props.end(); ++i) { |
| if (i != props.begin()) result += ", "; |
| result += i->first + "=" + i->second; |
| } |
| result += "]"; |
| return result; |
| } |
| |
| void translate_results_to_graph(const parser_result& r, ::boost::detail::graph::mutate_graph* mg) { |
| typedef boost::detail::graph::node_t vertex; |
| typedef boost::detail::graph::edge_t edge; |
| for (std::map<node_name, properties>::const_iterator i = r.nodes.begin(); i != r.nodes.end(); ++i) { |
| // std::cerr << i->first << " " << props_to_string(i->second) << std::endl; |
| mg->do_add_vertex(i->first); |
| for (properties::const_iterator j = i->second.begin(); j != i->second.end(); ++j) { |
| mg->set_node_property(j->first, i->first, j->second); |
| } |
| } |
| for (std::vector<edge_info>::const_iterator i = r.edges.begin(); i != r.edges.end(); ++i) { |
| const edge_info& ei = *i; |
| // std::cerr << ei.source << " -> " << ei.target << " " << props_to_string(ei.props) << std::endl; |
| edge e = edge::new_edge(); |
| mg->do_add_edge(e, ei.source.name, ei.target.name); |
| for (properties::const_iterator j = ei.props.begin(); j != ei.props.end(); ++j) { |
| mg->set_edge_property(j->first, e, j->second); |
| } |
| } |
| std::map<subgraph_name, properties>::const_iterator root_graph_props_i = r.graph_props.find("___root___"); |
| assert (root_graph_props_i != r.graph_props.end()); // Should not happen |
| const properties& root_graph_props = root_graph_props_i->second; |
| // std::cerr << "ending graph " << props_to_string(root_graph_props) << std::endl; |
| for (properties::const_iterator i = root_graph_props.begin(); i != root_graph_props.end(); ++i) { |
| mg->set_graph_property(i->first, i->second); |
| } |
| } |
| |
| } // end namespace read_graphviz_detail |
| |
| namespace detail { |
| namespace graph { |
| |
| BOOST_GRAPH_DECL bool read_graphviz_new(const std::string& str, boost::detail::graph::mutate_graph* mg) { |
| read_graphviz_detail::parser_result parsed_file; |
| read_graphviz_detail::parse_graphviz_from_string(str, parsed_file, mg->is_directed()); |
| read_graphviz_detail::translate_results_to_graph(parsed_file, mg); |
| return true; |
| } |
| |
| } // end namespace graph |
| } // end namespace detail |
| |
| } // end namespace boost |
| |
| // GraphViz format notes (tested using "dot version 1.13 (v16) (Mon August 23, |
| // 2004)", grammar from references in read_graphviz_new.hpp): |
| |
| // Subgraphs don't nest (all a0 subgraphs are the same thing), but a node or |
| // subgraph can have multiple parents (sources online say that the layout |
| // algorithms can't handle non-tree structures of clusters, but it seems to |
| // read them the same from the file). The containment relation is required to |
| // be a DAG, though; it appears that a node or subgraph can't be moved into an |
| // ancestor of a subgraph where it already was (we don't enforce that but do a |
| // DFS when finding members to prevent cycles). Nodes get their properties by |
| // when they are first mentioned, and can only have them overridden after that |
| // by explicit properties on that particular node. Closing and reopening the |
| // same subgraph name adds to its members, and graph properties and node/edge |
| // defaults are preserved in that subgraph. The members of a subgraph used in |
| // an edge are gathered when the edge is read, even if new members are added to |
| // the subgraph later. Ports are allowed in a lot more places in the grammar |
| // than Dot uses. For example, declaring a node seems to ignore ports, and I |
| // don't think it's possible to set properties on a particular port. Adding an |
| // edge between two ports on the same node seems to make Dot unhappy (crashed |
| // for me). |
| |
| // Test graph for GraphViz behavior on strange combinations of subgraphs and |
| // such. I don't have anywhere else to put this file. |
| |
| #if 0 |
| dIGRaph foo { |
| node [color=blue] |
| subgraph a -> b |
| subgraph a {c} |
| subgraph a -> d |
| subgraph a {node [color=red]; e} |
| subgraph a -> f |
| subgraph a {g} -> h |
| subgraph a {node [color=green]; i} -> j |
| subgraph a {node [color=yellow]} |
| |
| subgraph a0 {node [color=black]; subgraph a1 {node [color=white]}} |
| node [color=pink] zz |
| subgraph a0 {x1} |
| subgraph a0 {subgraph a1 {x2}} |
| |
| subgraph a0 -> x3 |
| subgraph a0 {subgraph a1 -> x3} |
| x3 |
| subgraph a0 {subgraph a0 {node [color=xxx]; x2} x7} |
| x2 [color=yyy] |
| subgraph cluster_ax {foo; subgraph a0} |
| subgraph a0 {foo2} |
| subgraph cluster_ax {foo3} |
| // subgraph a0 -> subgraph a0 |
| |
| bgcolor=yellow |
| subgraph cluster_a2 {y1} |
| // y1:n -> y1:(s,3)@se |
| y1@se [color=green] |
| y1@n [color=red] |
| } |
| #endif |