Line | Count | Source |
1 | | /* |
2 | | igraph library. |
3 | | Copyright (C) 2005-2020 The igraph development team |
4 | | |
5 | | This program is free software; you can redistribute it and/or modify |
6 | | it under the terms of the GNU General Public License as published by |
7 | | the Free Software Foundation; either version 2 of the License, or |
8 | | (at your option) any later version. |
9 | | |
10 | | This program is distributed in the hope that it will be useful, |
11 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | GNU General Public License for more details. |
14 | | |
15 | | You should have received a copy of the GNU General Public License |
16 | | along with this program; if not, write to the Free Software |
17 | | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
18 | | 02110-1301 USA |
19 | | |
20 | | */ |
21 | | |
22 | | #include "igraph_foreign.h" |
23 | | |
24 | | #include "igraph_attributes.h" |
25 | | #include "igraph_error.h" |
26 | | #include "igraph_interface.h" |
27 | | #include "igraph_memory.h" |
28 | | #include "igraph_version.h" |
29 | | |
30 | | #include "graph/attributes.h" |
31 | | #include "internal/hacks.h" /* strcasecmp & strdup */ |
32 | | #include "math/safe_intop.h" /* IGRAPH_MAX_EXACT_REAL */ |
33 | | |
34 | | #include <ctype.h> |
35 | | #include <string.h> |
36 | | |
37 | 1.16M | #define CHECK(cmd) do { int ret=cmd; if (ret<0) IGRAPH_ERROR("Writing DOT format failed.", IGRAPH_EFILE); } while (0) |
38 | | |
39 | 837k | static igraph_error_t dot_escape(const char *orig, igraph_vector_char_t* result) { |
40 | | /* do we have to escape the string at all? */ |
41 | 837k | igraph_int_t i, j, len = strlen(orig), newlen = 0; |
42 | 837k | igraph_bool_t need_quote = false, is_number = true; |
43 | | |
44 | | /* first, check whether the string is equal to some reserved word, or empty */ |
45 | 837k | if (!strcasecmp(orig, "graph") || !strcasecmp(orig, "digraph") || |
46 | 837k | !strcasecmp(orig, "node") || !strcasecmp(orig, "edge") || |
47 | 837k | !strcasecmp(orig, "strict") || !strcasecmp(orig, "subgraph") || len == 0) { |
48 | 108k | need_quote = true; |
49 | 108k | is_number = false; |
50 | 108k | } |
51 | | |
52 | | /* next, check whether we need to escape the string for any other reason. |
53 | | * Also update is_number and newlen */ |
54 | 258M | for (i = 0; i < len; i++) { |
55 | 257M | if (isdigit(orig[i])) { |
56 | 8.33M | newlen++; |
57 | 249M | } else if (orig[i] == '-' && i == 0) { |
58 | 3.55k | newlen++; |
59 | 249M | } else if (orig[i] == '.') { |
60 | 1.08M | if (is_number) { |
61 | 3.76k | newlen++; |
62 | 1.07M | } else { |
63 | 1.07M | need_quote = true; |
64 | 1.07M | newlen++; |
65 | 1.07M | } |
66 | 248M | } else if (orig[i] == '_') { |
67 | 3.12M | is_number = false; newlen++; |
68 | 245M | } else if (orig[i] == '\\' || orig[i] == '"' || orig[i] == '\n') { |
69 | 26.3M | need_quote = true; is_number = false; newlen += 2; /* will be escaped */ |
70 | 218M | } else if (isalpha(orig[i])) { |
71 | 38.9M | is_number = false; newlen++; |
72 | 179M | } else { |
73 | 179M | is_number = false; need_quote = true; newlen++; |
74 | 179M | } |
75 | 257M | } |
76 | 837k | if (is_number && len > 0 && orig[len - 1] == '.') { |
77 | 485 | is_number = false; |
78 | 485 | } |
79 | 837k | if (!is_number && isdigit(orig[0])) { |
80 | 3.72k | need_quote = true; |
81 | 3.72k | } |
82 | | |
83 | 837k | if (is_number || !need_quote) { |
84 | 683k | IGRAPH_CHECK(igraph_vector_char_resize(result, newlen + 1)); |
85 | 683k | memcpy(VECTOR(*result), orig, newlen); |
86 | 683k | } else { |
87 | 154k | newlen += 2; |
88 | 154k | IGRAPH_CHECK(igraph_vector_char_resize(result, newlen + 1)); |
89 | 154k | VECTOR(*result)[0] = '"'; |
90 | 154k | VECTOR(*result)[newlen - 1] = '"'; |
91 | | |
92 | | /* Escape quotes, backslashes and newlines. |
93 | | * Even though the format spec at https://graphviz.org/doc/info/lang.html |
94 | | * claims that only quotes need escaping, escaping backslashes appears to |
95 | | * be necessary as well for GraphViz to render labels correctly. |
96 | | * Tested with GraphViz 2.50. */ |
97 | 245M | for (i = 0, j = 1; i < len; i++) { |
98 | 245M | if (orig[i] == '\n') { |
99 | 10.9M | VECTOR(*result)[j++] = '\\'; |
100 | 10.9M | VECTOR(*result)[j++] = 'n'; |
101 | 10.9M | continue; |
102 | 10.9M | } |
103 | 234M | if (orig[i] == '\\' || orig[i] == '"') { |
104 | 15.4M | VECTOR(*result)[j++] = '\\'; |
105 | 15.4M | } |
106 | 234M | VECTOR(*result)[j++] = orig[i]; |
107 | 234M | } |
108 | 154k | } |
109 | 837k | VECTOR(*result)[newlen] = 0; |
110 | | |
111 | 837k | return IGRAPH_SUCCESS; |
112 | 837k | } |
113 | | |
114 | | /* Writes exactly representable integral values in standard integer notation, without decimal points or e-notation. |
115 | | * Floating point values that are written with e-notation are quoted, otherwise the Graphviz parser cannot handle them. |
116 | | */ |
117 | 76.2k | static igraph_error_t fprint_integral_or_precise(FILE *file, igraph_real_t x, igraph_vector_char_t *buf) { |
118 | 76.2k | if (fabs(x) <= IGRAPH_MAX_EXACT_REAL && floor(x) == x) { |
119 | | /* write exactly representable integral values in standard integer notation; |
120 | | * the above conditional skips +-Inf and NaN */ |
121 | 4.47k | CHECK(fprintf(file, "%.f", x)); |
122 | 71.8k | } else { |
123 | | /* write as precise float and quote if necessary */ |
124 | 71.8k | char str[50]; /* large enough to hold any precisely printed real */ |
125 | 71.8k | CHECK(igraph_real_snprintf_precise(str, sizeof(str) / sizeof(str[0]), x)); |
126 | 71.8k | IGRAPH_CHECK(dot_escape(str, buf)); |
127 | 71.8k | CHECK(fputs(VECTOR(*buf), file)); |
128 | 71.8k | } |
129 | 76.2k | return IGRAPH_SUCCESS; |
130 | 76.2k | } |
131 | | |
132 | | /** |
133 | | * \function igraph_write_graph_dot |
134 | | * \brief Write the graph to a stream in DOT format. |
135 | | * |
136 | | * </para><para> |
137 | | * DOT is the format used by the widely known GraphViz software, see |
138 | | * http://www.graphviz.org for details. The grammar of the DOT format |
139 | | * can be found here: http://www.graphviz.org/doc/info/lang.html |
140 | | * |
141 | | * </para><para> |
142 | | * This is only a preliminary implementation, no visualization |
143 | | * information is written. |
144 | | * |
145 | | * </para><para> |
146 | | * This format is meant solely for interoperability with Graphviz. |
147 | | * It is not recommended for data exchange or archival. |
148 | | * |
149 | | * \param graph The graph to write to the stream. |
150 | | * \param outstream The stream to write the file to. |
151 | | * \return Error code. |
152 | | * |
153 | | * Time complexity: should be proportional to the number of characters written |
154 | | * to the file. |
155 | | * |
156 | | * \sa \ref igraph_write_graph_graphml() for a more modern format. |
157 | | * |
158 | | * \example examples/simple/dot.c |
159 | | */ |
160 | 3.51k | igraph_error_t igraph_write_graph_dot(const igraph_t *graph, FILE* outstream) { |
161 | 3.51k | const igraph_int_t no_of_nodes = igraph_vcount(graph); |
162 | 3.51k | const igraph_int_t no_of_edges = igraph_ecount(graph); |
163 | 3.51k | const igraph_bool_t directed = igraph_is_directed(graph); |
164 | 3.51k | const char *edgeop = directed ? "->" : "--"; |
165 | 3.51k | igraph_strvector_t gnames, vnames, enames; |
166 | 3.51k | igraph_vector_int_t gtypes, vtypes, etypes; |
167 | 3.51k | igraph_vector_t numv; |
168 | 3.51k | igraph_strvector_t strv; |
169 | 3.51k | igraph_vector_bool_t boolv; |
170 | 3.51k | igraph_vector_char_t buf, buf2; |
171 | | |
172 | 3.51k | IGRAPH_STRVECTOR_INIT_FINALLY(&gnames, 0); |
173 | 3.51k | IGRAPH_STRVECTOR_INIT_FINALLY(&vnames, 0); |
174 | 3.51k | IGRAPH_STRVECTOR_INIT_FINALLY(&enames, 0); |
175 | 3.51k | IGRAPH_VECTOR_INT_INIT_FINALLY(>ypes, 0); |
176 | 3.51k | IGRAPH_VECTOR_INT_INIT_FINALLY(&vtypes, 0); |
177 | 3.51k | IGRAPH_VECTOR_INT_INIT_FINALLY(&etypes, 0); |
178 | 3.51k | IGRAPH_CHECK(igraph_i_attribute_get_info(graph, |
179 | 3.51k | &gnames, >ypes, |
180 | 3.51k | &vnames, &vtypes, |
181 | 3.51k | &enames, &etypes)); |
182 | | |
183 | 3.51k | IGRAPH_VECTOR_INIT_FINALLY(&numv, 0); |
184 | 3.51k | IGRAPH_STRVECTOR_INIT_FINALLY(&strv, 0); |
185 | 3.51k | IGRAPH_VECTOR_BOOL_INIT_FINALLY(&boolv, 0); |
186 | | |
187 | 3.51k | IGRAPH_VECTOR_CHAR_INIT_FINALLY(&buf, 0); |
188 | 3.51k | IGRAPH_VECTOR_CHAR_INIT_FINALLY(&buf2, 0); |
189 | | |
190 | 3.51k | CHECK(fprintf(outstream, "/* Created by igraph %s */\n", IGRAPH_VERSION)); |
191 | | |
192 | 3.51k | if (directed) { |
193 | 138 | CHECK(fprintf(outstream, "digraph {\n")); |
194 | 3.37k | } else { |
195 | 3.37k | CHECK(fprintf(outstream, "graph {\n")); |
196 | 3.37k | } |
197 | | |
198 | | /* Write the graph attributes */ |
199 | 3.51k | if (igraph_vector_int_size(>ypes) > 0) { |
200 | 520 | CHECK(fprintf(outstream, " graph [\n")); |
201 | 1.36k | for (igraph_int_t i = 0; i < igraph_vector_int_size(>ypes); i++) { |
202 | 846 | const char *name; |
203 | 846 | name = igraph_strvector_get(&gnames, i); |
204 | 846 | IGRAPH_CHECK(dot_escape(name, &buf)); |
205 | 846 | if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { |
206 | 85 | IGRAPH_CHECK(igraph_i_attribute_get_numeric_graph_attr(graph, name, &numv)); |
207 | 85 | CHECK(fprintf(outstream, " %s=", VECTOR(buf))); |
208 | 85 | IGRAPH_CHECK(fprint_integral_or_precise(outstream, VECTOR(numv)[0], &buf)); |
209 | 85 | CHECK(fputc('\n', outstream)); |
210 | 761 | } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_STRING) { |
211 | 611 | const char *s; |
212 | 611 | IGRAPH_CHECK(igraph_i_attribute_get_string_graph_attr(graph, name, &strv)); |
213 | 611 | s = igraph_strvector_get(&strv, 0); |
214 | 611 | IGRAPH_CHECK(dot_escape(s, &buf2)); |
215 | 611 | CHECK(fprintf(outstream, " %s=%s\n", VECTOR(buf), VECTOR(buf2))); |
216 | 611 | } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { |
217 | 150 | IGRAPH_CHECK(igraph_i_attribute_get_bool_graph_attr(graph, name, &boolv)); |
218 | 150 | CHECK(fprintf(outstream, " %s=%d\n", VECTOR(buf), VECTOR(boolv)[0] ? 1 : 0)); |
219 | 150 | IGRAPH_WARNING("Boolean graph attribute was converted to numeric."); |
220 | 150 | } else { |
221 | 0 | IGRAPH_WARNING("A non-numeric, non-string, non-boolean graph attribute was ignored."); |
222 | 0 | } |
223 | 846 | } |
224 | 520 | CHECK(fprintf(outstream, " ];\n")); |
225 | 520 | } |
226 | | |
227 | | /* Write the vertices */ |
228 | 3.51k | if (igraph_vector_int_size(&vtypes) > 0) { |
229 | 36.2k | for (igraph_int_t i = 0; i < no_of_nodes; i++) { |
230 | 32.7k | CHECK(fprintf(outstream, " %" IGRAPH_PRId " [\n", i)); |
231 | 84.2k | for (igraph_int_t j = 0; j < igraph_vector_int_size(&vtypes); j++) { |
232 | 51.5k | const char *name; |
233 | 51.5k | name = igraph_strvector_get(&vnames, j); |
234 | 51.5k | IGRAPH_CHECK(dot_escape(name, &buf)); |
235 | 51.5k | if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) { |
236 | 4.90k | IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr(graph, name, igraph_vss_1(i), &numv)); |
237 | 4.90k | CHECK(fprintf(outstream, " %s=", VECTOR(buf))); |
238 | 4.90k | IGRAPH_CHECK(fprint_integral_or_precise(outstream, VECTOR(numv)[0], &buf)); |
239 | 4.90k | CHECK(fputc('\n', outstream)); |
240 | 46.6k | } else if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_STRING) { |
241 | 42.1k | const char *s; |
242 | 42.1k | IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, name, igraph_vss_1(i), &strv)); |
243 | 42.1k | s = igraph_strvector_get(&strv, 0); |
244 | 42.1k | IGRAPH_CHECK(dot_escape(s, &buf2)); |
245 | 42.1k | CHECK(fprintf(outstream, " %s=%s\n", VECTOR(buf), VECTOR(buf2))); |
246 | 42.1k | } else if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_BOOLEAN) { |
247 | 4.52k | IGRAPH_CHECK(igraph_i_attribute_get_bool_vertex_attr(graph, name, igraph_vss_1(i), &boolv)); |
248 | 4.52k | CHECK(fprintf(outstream, " %s=%d\n", VECTOR(buf), VECTOR(boolv)[0] ? 1 : 0)); |
249 | 4.52k | IGRAPH_WARNING("A boolean vertex attribute was converted to numeric."); |
250 | 4.52k | } else { |
251 | 0 | IGRAPH_WARNING("A non-numeric, non-string, non-boolean vertex attribute was ignored."); |
252 | 0 | } |
253 | 51.5k | } |
254 | 32.7k | CHECK(fprintf(outstream, " ];\n")); |
255 | 32.7k | } |
256 | 3.50k | } else { |
257 | 243 | for (igraph_int_t i = 0; i < no_of_nodes; i++) { |
258 | 230 | CHECK(fprintf(outstream, " %" IGRAPH_PRId ";\n", i)); |
259 | 230 | } |
260 | 13 | } |
261 | 3.51k | CHECK(fprintf(outstream, "\n")); |
262 | | |
263 | | /* Write the edges */ |
264 | 3.51k | if (igraph_vector_int_size(&etypes) > 0) { |
265 | 161k | for (igraph_int_t i = 0; i < no_of_edges; i++) { |
266 | 159k | igraph_int_t from = IGRAPH_FROM(graph, i); |
267 | 159k | igraph_int_t to = IGRAPH_TO(graph, i); |
268 | 159k | CHECK(fprintf(outstream, " %" IGRAPH_PRId " %s %" IGRAPH_PRId " [\n", from, edgeop, to)); |
269 | 533k | for (igraph_int_t j = 0; j < igraph_vector_int_size(&etypes); j++) { |
270 | 373k | const char *name; |
271 | 373k | name = igraph_strvector_get(&enames, j); |
272 | 373k | IGRAPH_CHECK(dot_escape(name, &buf)); |
273 | 373k | if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) { |
274 | 71.2k | IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, |
275 | 71.2k | name, igraph_ess_1(i), &numv)); |
276 | 71.2k | CHECK(fprintf(outstream, " %s=", VECTOR(buf))); |
277 | 71.2k | IGRAPH_CHECK(fprint_integral_or_precise(outstream, VECTOR(numv)[0], &buf)); |
278 | 71.2k | CHECK(fputc('\n', outstream)); |
279 | 302k | } else if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_STRING) { |
280 | 296k | const char *s; |
281 | 296k | IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr(graph, |
282 | 296k | name, igraph_ess_1(i), &strv)); |
283 | 296k | s = igraph_strvector_get(&strv, 0); |
284 | 296k | IGRAPH_CHECK(dot_escape(s, &buf2)); |
285 | 296k | CHECK(fprintf(outstream, " %s=%s\n", VECTOR(buf), VECTOR(buf2))); |
286 | 296k | } else if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_BOOLEAN) { |
287 | 5.77k | IGRAPH_CHECK(igraph_i_attribute_get_bool_edge_attr(graph, |
288 | 5.77k | name, igraph_ess_1(i), &boolv)); |
289 | 5.77k | CHECK(fprintf(outstream, " %s=%d\n", VECTOR(buf), VECTOR(boolv)[0] ? 1 : 0)); |
290 | 5.77k | IGRAPH_WARNING("A boolean edge attribute was converted to numeric."); |
291 | 5.77k | } else { |
292 | 0 | IGRAPH_WARNING("A non-numeric, non-string graph attribute ignored."); |
293 | 0 | } |
294 | 373k | } |
295 | 159k | CHECK(fprintf(outstream, " ];\n")); |
296 | 159k | } |
297 | 1.83k | } else { |
298 | 111k | for (igraph_int_t i = 0; i < no_of_edges; i++) { |
299 | 109k | igraph_int_t from = IGRAPH_FROM(graph, i); |
300 | 109k | igraph_int_t to = IGRAPH_TO(graph, i); |
301 | 109k | CHECK(fprintf(outstream, " %" IGRAPH_PRId " %s %" IGRAPH_PRId ";\n", from, edgeop, to)); |
302 | 109k | } |
303 | 1.83k | } |
304 | 3.51k | CHECK(fprintf(outstream, "}\n")); |
305 | | |
306 | 3.51k | igraph_vector_char_destroy(&buf2); |
307 | 3.51k | igraph_vector_char_destroy(&buf); |
308 | 3.51k | igraph_vector_bool_destroy(&boolv); |
309 | 3.51k | igraph_strvector_destroy(&strv); |
310 | 3.51k | igraph_vector_destroy(&numv); |
311 | 3.51k | igraph_vector_int_destroy(&etypes); |
312 | 3.51k | igraph_vector_int_destroy(&vtypes); |
313 | 3.51k | igraph_vector_int_destroy(>ypes); |
314 | 3.51k | igraph_strvector_destroy(&enames); |
315 | 3.51k | igraph_strvector_destroy(&vnames); |
316 | 3.51k | igraph_strvector_destroy(&gnames); |
317 | 3.51k | IGRAPH_FINALLY_CLEAN(11); |
318 | | |
319 | 3.51k | return IGRAPH_SUCCESS; |
320 | 3.51k | } |
321 | | |
322 | | #undef CHECK |