1 | /* $Id$ $Revision$ */ |
2 | /* vim:set shiftwidth=4 ts=8: */ |
3 | |
4 | /************************************************************************* |
5 | * Copyright (c) 2015 AT&T Intellectual Property |
6 | * All rights reserved. This program and the accompanying materials |
7 | * are made available under the terms of the Eclipse Public License v1.0 |
8 | * which accompanies this distribution, and is available at |
9 | * http://www.eclipse.org/legal/epl-v10.html |
10 | * |
11 | * Contributors: See CVS logs. Details at http://www.graphviz.org/ |
12 | *************************************************************************/ |
13 | |
14 | #include "config.h" |
15 | |
16 | #ifdef _WIN32 |
17 | #include <io.h> |
18 | #include "compat.h" |
19 | #endif |
20 | |
21 | #include <stdarg.h> |
22 | #include <stdlib.h> |
23 | #include <string.h> |
24 | #include <ctype.h> |
25 | |
26 | #include "macros.h" |
27 | #include "const.h" |
28 | #include "xdot.h" |
29 | |
30 | #include "gvplugin_render.h" |
31 | #include "gvplugin_device.h" |
32 | #include "agxbuf.h" |
33 | #include "utils.h" |
34 | #include "gvc.h" |
35 | #include "gvio.h" |
36 | #include "gvcint.h" |
37 | |
38 | typedef enum { |
39 | FORMAT_JSON, |
40 | FORMAT_JSON0, |
41 | FORMAT_DOT_JSON, |
42 | FORMAT_XDOT_JSON, |
43 | } format_type; |
44 | |
45 | typedef struct { |
46 | int Level; |
47 | boolean isLatin; |
48 | boolean doXDot; |
49 | boolean Attrs_not_written_flag; |
50 | } state_t; |
51 | |
52 | typedef struct { |
53 | Agrec_t h; |
54 | int id; |
55 | } gvid_t; |
56 | |
57 | #define ID "id" |
58 | #define ND_gid(n) (((gvid_t*)aggetrec(n, ID, FALSE))->id) |
59 | #define ED_gid(n) (((gvid_t*)aggetrec(n, ID, FALSE))->id) |
60 | #define GD_gid(n) (((gvid_t*)aggetrec(n, ID, FALSE))->id) |
61 | |
62 | #define IS_CLUSTER(s) (!strncmp(agnameof(s), "cluster", 7)) |
63 | |
64 | static void json_begin_graph(GVJ_t *job) |
65 | { |
66 | if (job->render.id == FORMAT_JSON) { |
67 | GVC_t* gvc = gvCloneGVC (job->gvc); |
68 | graph_t *g = job->obj->u.g; |
69 | gvRender (gvc, g, "xdot" , NULL); |
70 | gvFreeCloneGVC (gvc); |
71 | } |
72 | else if (job->render.id == FORMAT_JSON0) { |
73 | attach_attrs(job->gvc->g); |
74 | } |
75 | } |
76 | |
77 | #define LOCALNAMEPREFIX '%' |
78 | |
79 | /* stoj: |
80 | * Convert dot string to a valid json string embedded in double quotes. |
81 | */ |
82 | static char* stoj (char* ins, state_t* sp) |
83 | { |
84 | char* s; |
85 | char* input; |
86 | static agxbuf xb; |
87 | unsigned char c; |
88 | |
89 | if (sp->isLatin) |
90 | input = latin1ToUTF8 (ins); |
91 | else |
92 | input = ins; |
93 | |
94 | if (xb.buf == NULL) |
95 | agxbinit(&xb, BUFSIZ, NULL); |
96 | for (s = input; (c = *s); s++) { |
97 | switch (c) { |
98 | case '"' : |
99 | agxbput(&xb, "\\\"" ); |
100 | break; |
101 | case '\\' : |
102 | agxbput(&xb, "\\\\" ); |
103 | break; |
104 | case '/' : |
105 | agxbput(&xb, "\\/" ); |
106 | break; |
107 | case '\b' : |
108 | agxbput(&xb, "\\b" ); |
109 | break; |
110 | case '\f' : |
111 | agxbput(&xb, "\\f" ); |
112 | break; |
113 | case '\n' : |
114 | agxbput(&xb, "\\n" ); |
115 | break; |
116 | case '\r' : |
117 | agxbput(&xb, "\\r" ); |
118 | break; |
119 | case '\t' : |
120 | agxbput(&xb, "\\t" ); |
121 | break; |
122 | default : |
123 | agxbputc(&xb, c); |
124 | break; |
125 | } |
126 | } |
127 | s = agxbuse(&xb); |
128 | |
129 | if (sp->isLatin) |
130 | free (input); |
131 | return s; |
132 | } |
133 | |
134 | static void indent(GVJ_t * job, int level) |
135 | { |
136 | int i; |
137 | for (i = level; i > 0; i--) |
138 | gvputs(job, " " ); |
139 | } |
140 | |
141 | static void set_attrwf(Agraph_t * g, int toplevel, int value) |
142 | { |
143 | Agraph_t *subg; |
144 | Agnode_t *n; |
145 | Agedge_t *e; |
146 | |
147 | AGATTRWF(g) = value; |
148 | for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) { |
149 | set_attrwf(subg, FALSE, value); |
150 | } |
151 | if (toplevel) { |
152 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) { |
153 | AGATTRWF(n) = value; |
154 | for (e = agfstout(g, n); e; e = agnxtout(g, e)) |
155 | AGATTRWF(e) = value; |
156 | } |
157 | } |
158 | } |
159 | |
160 | static void write_polyline (GVJ_t * job, xdot_polyline* polyline) |
161 | { |
162 | int i; |
163 | int cnt = polyline->cnt; |
164 | xdot_point* pts = polyline->pts; |
165 | |
166 | gvprintf(job, "\"points\": [" ); |
167 | for (i = 0; i < cnt; i++) { |
168 | if (i > 0) gvprintf(job, "," ); |
169 | gvprintf(job, "[%.03f,%.03f]" , pts[i].x, pts[i].y); |
170 | } |
171 | gvprintf(job, "]\n" ); |
172 | } |
173 | |
174 | static void write_stops (GVJ_t * job, int n_stops, xdot_color_stop* stp, state_t* sp) |
175 | { |
176 | int i; |
177 | |
178 | gvprintf(job, "\"stops\": [" ); |
179 | for (i = 0; i < n_stops; i++) { |
180 | if (i > 0) gvprintf(job, "," ); |
181 | gvprintf(job, "{\"frac\": %.03f, \"color\": \"%s\"}" , |
182 | stp[i].frac, stoj(stp[i].color, sp)); |
183 | } |
184 | gvprintf(job, "]\n" ); |
185 | } |
186 | |
187 | static void write_radial_grad (GVJ_t * job, xdot_radial_grad* rg, state_t* sp) |
188 | { |
189 | indent(job, sp->Level); |
190 | gvprintf(job, "\"p0\": [%.03f,%.03f,%.03f],\n" , rg->x0, rg->y0, rg->r0); |
191 | indent(job, sp->Level); |
192 | gvprintf(job, "\"p1\": [%.03f,%.03f,%.03f],\n" , rg->x1, rg->y1, rg->r1); |
193 | indent(job, sp->Level); |
194 | write_stops (job, rg->n_stops, rg->stops, sp); |
195 | } |
196 | |
197 | static void write_linear_grad (GVJ_t * job, xdot_linear_grad* lg, state_t* sp) |
198 | { |
199 | indent(job, sp->Level); |
200 | gvprintf(job, "\"p0\": [%.03f,%.03f],\n" , lg->x0, lg->y0); |
201 | indent(job, sp->Level); |
202 | gvprintf(job, "\"p1\": [%.03f,%.03f],\n" , lg->x1, lg->y1); |
203 | indent(job, sp->Level); |
204 | write_stops (job, lg->n_stops, lg->stops, sp); |
205 | } |
206 | |
207 | static void write_xdot (xdot_op * op, GVJ_t * job, state_t* sp) |
208 | { |
209 | indent(job, sp->Level++); |
210 | gvputs(job, "{\n" ); |
211 | indent(job, sp->Level); |
212 | |
213 | switch (op->kind) { |
214 | case xd_filled_ellipse : |
215 | case xd_unfilled_ellipse : |
216 | gvprintf(job, "\"op\": \"%c\",\n" , |
217 | (op->kind == xd_filled_ellipse ? 'E' : 'e')); |
218 | indent(job, sp->Level); |
219 | gvprintf(job, "\"rect\": [%.03f,%.03f,%.03f,%.03f]\n" , |
220 | op->u.ellipse.x, op->u.ellipse.y, op->u.ellipse.w, op->u.ellipse.h); |
221 | break; |
222 | case xd_filled_polygon : |
223 | case xd_unfilled_polygon : |
224 | gvprintf(job, "\"op\": \"%c\",\n" , |
225 | (op->kind == xd_filled_polygon ? 'P' : 'p')); |
226 | indent(job, sp->Level); |
227 | write_polyline (job, &op->u.polygon); |
228 | break; |
229 | case xd_filled_bezier : |
230 | case xd_unfilled_bezier : |
231 | gvprintf(job, "\"op\": \"%c\",\n" , |
232 | (op->kind == xd_filled_bezier ? 'B' : 'b')); |
233 | indent(job, sp->Level); |
234 | write_polyline (job, &op->u.bezier); |
235 | break; |
236 | case xd_polyline : |
237 | gvprintf(job, "\"op\": \"L\",\n" ); |
238 | indent(job, sp->Level); |
239 | write_polyline (job, &op->u.polyline); |
240 | break; |
241 | case xd_text : |
242 | gvprintf(job, "\"op\": \"T\",\n" ); |
243 | indent(job, sp->Level); |
244 | gvprintf(job, "\"pt\": [%.03f,%.03f],\n" , op->u.text.x, op->u.text.y); |
245 | indent(job, sp->Level); |
246 | gvprintf(job, "\"align\": \"%c\",\n" , |
247 | (op->u.text.align == xd_left? 'l' : |
248 | (op->u.text.align == xd_center ? 'c' : 'r'))); |
249 | indent(job, sp->Level); |
250 | gvprintf(job, "\"width\": %.03f,\n" , op->u.text.width); |
251 | indent(job, sp->Level); |
252 | gvprintf(job, "\"text\": \"%s\"\n" , stoj(op->u.text.text, sp)); |
253 | break; |
254 | case xd_fill_color : |
255 | case xd_pen_color : |
256 | gvprintf(job, "\"op\": \"%c\",\n" , |
257 | (op->kind == xd_fill_color ? 'C' : 'c')); |
258 | indent(job, sp->Level); |
259 | gvprintf(job, "\"grad\": \"none\",\n" ); |
260 | indent(job, sp->Level); |
261 | gvprintf(job, "\"color\": \"%s\"\n" , stoj(op->u.color, sp)); |
262 | break; |
263 | case xd_grad_pen_color : |
264 | case xd_grad_fill_color : |
265 | gvprintf(job, "\"op\": \"%c\",\n" , |
266 | (op->kind == xd_grad_fill_color ? 'C' : 'c')); |
267 | indent(job, sp->Level); |
268 | if (op->u.grad_color.type == xd_none) { |
269 | gvprintf(job, "\"grad\": \"none\",\n" ); |
270 | indent(job, sp->Level); |
271 | gvprintf(job, "\"color\": \"%s\"\n" , |
272 | stoj(op->u.grad_color.u.clr, sp)); |
273 | } |
274 | else { |
275 | if (op->u.grad_color.type == xd_linear) { |
276 | gvprintf(job, "\"grad\": \"linear\",\n" ); |
277 | indent(job, sp->Level); |
278 | write_linear_grad (job, &op->u.grad_color.u.ling, sp); |
279 | } |
280 | else { |
281 | gvprintf(job, "\"grad\": \"radial\",\n" ); |
282 | indent(job, sp->Level); |
283 | write_radial_grad (job, &op->u.grad_color.u.ring, sp); |
284 | } |
285 | } |
286 | break; |
287 | case xd_font : |
288 | gvprintf(job, "\"op\": \"F\",\n" ); |
289 | indent(job, sp->Level); |
290 | gvprintf(job, "\"size\": %.03f,\n" , op->u.font.size); |
291 | indent(job, sp->Level); |
292 | gvprintf(job, "\"face\": \"%s\"\n" , stoj(op->u.font.name, sp)); |
293 | break; |
294 | case xd_style : |
295 | gvprintf(job, "\"op\": \"S\",\n" ); |
296 | indent(job, sp->Level); |
297 | gvprintf(job, "\"style\": \"%s\"\n" , stoj(op->u.style, sp)); |
298 | break; |
299 | case xd_image : |
300 | break; |
301 | case xd_fontchar : |
302 | gvprintf(job, "\"op\": \"t\",\n" ); |
303 | indent(job, sp->Level); |
304 | gvprintf(job, "\"fontchar\": %d\n" , op->u.fontchar); |
305 | break; |
306 | } |
307 | sp->Level--; |
308 | indent(job, sp->Level); |
309 | gvputs(job, "}" ); |
310 | } |
311 | |
312 | static void write_xdots (char * val, GVJ_t * job, state_t* sp) |
313 | { |
314 | xdot* cmds; |
315 | int i; |
316 | int not_first = 0; |
317 | |
318 | if (!val || (*val == '\0')) return; |
319 | |
320 | cmds = parseXDot(val); |
321 | if (!cmds) { |
322 | agerr(AGWARN, "Could not parse xdot \"%s\"\n" , val); |
323 | return; |
324 | } |
325 | |
326 | gvputs(job, "\n" ); |
327 | indent(job, sp->Level++); |
328 | gvputs(job, "[\n" ); |
329 | for (i = 0; i < cmds->cnt; i++) { |
330 | if (not_first) |
331 | gvputs(job, ",\n" ); |
332 | else |
333 | not_first = 1; |
334 | write_xdot (cmds->ops+i, job, sp); |
335 | } |
336 | sp->Level--; |
337 | gvputs(job, "\n" ); |
338 | indent(job, sp->Level); |
339 | gvputs(job, "]" ); |
340 | freeXDot(cmds); |
341 | } |
342 | |
343 | static int isXDot (char* name) |
344 | { |
345 | return ((*name++ == '_') && |
346 | (streq(name,"draw_" ) || streq(name,"ldraw_" ) || |
347 | streq(name,"hdraw_" ) || streq(name,"tdraw_" ) || |
348 | streq(name,"hldraw_" ) || streq(name,"tldraw_" ))); |
349 | } |
350 | |
351 | static void write_attrs(Agobj_t * obj, GVJ_t * job, state_t* sp) |
352 | { |
353 | Agraph_t* g = agroot(obj); |
354 | int type = AGTYPE(obj); |
355 | char* attrval; |
356 | Agsym_t* sym = agnxtattr(g, type, NULL); |
357 | if (!sym) return; |
358 | |
359 | for (; sym; sym = agnxtattr(g, type, sym)) { |
360 | if (!(attrval = agxget(obj, sym))) continue; |
361 | if ((*attrval == '\0') && !streq(sym->name, "label" )) continue; |
362 | gvputs(job, ",\n" ); |
363 | indent(job, sp->Level); |
364 | gvprintf(job, "\"%s\": " , stoj(sym->name, sp)); |
365 | if (sp->doXDot && isXDot(sym->name)) |
366 | write_xdots(agxget(obj, sym), job, sp); |
367 | else |
368 | gvprintf(job, "\"%s\"" , stoj(agxget(obj, sym), sp)); |
369 | } |
370 | } |
371 | |
372 | static void write_hdr(Agraph_t * g, GVJ_t * job, int top, state_t* sp) |
373 | { |
374 | char *name; |
375 | |
376 | name = agnameof(g); |
377 | indent(job, sp->Level); |
378 | gvprintf(job, "\"name\": \"%s\"" , stoj (name, sp)); |
379 | |
380 | if (top) { |
381 | gvputs(job, ",\n" ); |
382 | indent(job, sp->Level); |
383 | gvprintf(job, "\"directed\": %s,\n" , (agisdirected(g)?"true" :"false" )); |
384 | indent(job, sp->Level); |
385 | gvprintf(job, "\"strict\": %s" , (agisstrict(g)?"true" :"false" )); |
386 | } |
387 | } |
388 | |
389 | static void write_graph(Agraph_t * g, GVJ_t * job, int top, state_t* sp); |
390 | |
391 | static void write_subg(Agraph_t * g, GVJ_t * job, state_t* sp) |
392 | { |
393 | Agraph_t* sg; |
394 | |
395 | write_graph (g, job, FALSE, sp); |
396 | for (sg = agfstsubg(g); sg; sg = agnxtsubg(sg)) { |
397 | gvputs(job, ",\n" ); |
398 | write_subg(sg, job, sp); |
399 | } |
400 | } |
401 | |
402 | /* |
403 | static int write_subgs(Agraph_t * g, GVJ_t * job, int top, state_t* sp) |
404 | { |
405 | Agraph_t* sg; |
406 | int not_first = 0; |
407 | |
408 | sg = agfstsubg(g); |
409 | if (!sg) return 0; |
410 | |
411 | gvputs(job, ",\n"); |
412 | indent(job, sp->Level++); |
413 | gvputs(job, "\"subgraphs\": [\n"); |
414 | for (; sg; sg = agnxtsubg(sg)) { |
415 | if (not_first) |
416 | gvputs(job, ",\n"); |
417 | else |
418 | not_first = 1; |
419 | write_subg (sg, job, top, sp); |
420 | } |
421 | sp->Level--; |
422 | gvputs(job, "\n"); |
423 | indent(job, sp->Level); |
424 | gvputs(job, "]"); |
425 | return 1; |
426 | } |
427 | */ |
428 | |
429 | static int write_subgs(Agraph_t * g, GVJ_t * job, int top, state_t* sp) |
430 | { |
431 | Agraph_t* sg; |
432 | int not_first = 0; |
433 | |
434 | sg = agfstsubg(g); |
435 | if (!sg) return 0; |
436 | |
437 | gvputs(job, ",\n" ); |
438 | indent(job, sp->Level++); |
439 | if (top) |
440 | gvputs(job, "\"objects\": [\n" ); |
441 | else { |
442 | gvputs(job, "\"subgraphs\": [\n" ); |
443 | indent(job, sp->Level); |
444 | } |
445 | for (; sg; sg = agnxtsubg(sg)) { |
446 | if (not_first) |
447 | gvputs(job, ",\n" ); |
448 | else |
449 | not_first = 1; |
450 | if (top) |
451 | write_subg (sg, job, sp); |
452 | else |
453 | gvprintf(job, "%d" , GD_gid(sg)); |
454 | } |
455 | if (!top) { |
456 | sp->Level--; |
457 | gvputs(job, "\n" ); |
458 | indent(job, sp->Level); |
459 | gvputs(job, "]" ); |
460 | } |
461 | |
462 | return 1; |
463 | } |
464 | |
465 | static void write_edge(Agedge_t * e, GVJ_t * job, int top, state_t* sp) |
466 | { |
467 | if (top) { |
468 | indent(job, sp->Level++); |
469 | gvputs(job, "{\n" ); |
470 | indent(job, sp->Level); |
471 | gvprintf(job, "\"_gvid\": %d,\n" , ED_gid(e)); |
472 | indent(job, sp->Level); |
473 | gvprintf(job, "\"tail\": %d,\n" , ND_gid(agtail(e))); |
474 | indent(job, sp->Level); |
475 | gvprintf(job, "\"head\": %d" , ND_gid(aghead(e))); |
476 | write_attrs((Agobj_t*)e, job, sp); |
477 | gvputs(job, "\n" ); |
478 | sp->Level--; |
479 | indent(job, sp->Level); |
480 | gvputs(job, "}" ); |
481 | } |
482 | else { |
483 | gvprintf(job, "%d" , ED_gid(e)); |
484 | } |
485 | } |
486 | |
487 | static int write_edges(Agraph_t * g, GVJ_t * job, int top, state_t* sp) |
488 | { |
489 | Agnode_t* np; |
490 | Agedge_t* ep; |
491 | int not_first = 0; |
492 | |
493 | np = agfstnode(g); |
494 | if (!np) return 0; |
495 | ep = NULL; |
496 | /* find a first edge */ |
497 | for (; np; np = agnxtnode(g,np)) { |
498 | for (ep = agfstout(g, np); ep; ep = agnxtout(g,ep)) { |
499 | if (ep) break; |
500 | } |
501 | if (ep) break; |
502 | } |
503 | if (!ep) return 0; |
504 | |
505 | gvputs(job, ",\n" ); |
506 | indent(job, sp->Level++); |
507 | gvputs(job, "\"edges\": [\n" ); |
508 | if (!top) |
509 | indent(job, sp->Level); |
510 | for (; np; np = agnxtnode(g,np)) { |
511 | for (ep = agfstout(g, np); ep; ep = agnxtout(g,ep)) { |
512 | if (not_first) |
513 | if (top) |
514 | gvputs(job, ",\n" ); |
515 | else |
516 | gvputs(job, "," ); |
517 | else |
518 | not_first = 1; |
519 | write_edge(ep, job, top, sp); |
520 | } |
521 | } |
522 | sp->Level--; |
523 | gvputs(job, "\n" ); |
524 | indent(job, sp->Level); |
525 | gvputs(job, "]" ); |
526 | return 1; |
527 | } |
528 | |
529 | static void write_node(Agnode_t * n, GVJ_t * job, int top, state_t* sp) |
530 | { |
531 | if (top) { |
532 | indent(job, sp->Level++); |
533 | gvputs(job, "{\n" ); |
534 | indent(job, sp->Level); |
535 | gvprintf(job, "\"_gvid\": %d,\n" , ND_gid(n)); |
536 | indent(job, sp->Level); |
537 | gvprintf(job, "\"name\": \"%s\"" , stoj (agnameof(n), sp)); |
538 | write_attrs((Agobj_t*)n, job, sp); |
539 | gvputs(job, "\n" ); |
540 | sp->Level--; |
541 | indent(job, sp->Level); |
542 | gvputs(job, "}" ); |
543 | } |
544 | else { |
545 | gvprintf(job, "%d" , ND_gid(n)); |
546 | } |
547 | } |
548 | |
549 | static int write_nodes(Agraph_t * g, GVJ_t * job, int top, int has_subgs, state_t* sp) |
550 | { |
551 | Agnode_t* n; |
552 | int not_first = 0; |
553 | |
554 | n = agfstnode(g); |
555 | if (!n) { |
556 | if (has_subgs && top) { |
557 | sp->Level--; |
558 | gvputs(job, "\n" ); |
559 | indent(job, sp->Level); |
560 | gvputs(job, "]" ); |
561 | } |
562 | return 0; |
563 | } |
564 | gvputs(job, ",\n" ); |
565 | if (top) { |
566 | if (!has_subgs) { |
567 | indent(job, sp->Level++); |
568 | gvputs(job, "\"objects\": [\n" ); |
569 | } |
570 | } |
571 | else { |
572 | indent(job, sp->Level++); |
573 | gvputs(job, "\"nodes\": [\n" ); |
574 | indent(job, sp->Level); |
575 | } |
576 | for (; n; n = agnxtnode(g, n)) { |
577 | if (IS_CLUST_NODE(n)) continue; |
578 | if (not_first) |
579 | if (top) |
580 | gvputs(job, ",\n" ); |
581 | else |
582 | gvputs(job, "," ); |
583 | else |
584 | not_first = 1; |
585 | write_node (n, job, top, sp); |
586 | } |
587 | sp->Level--; |
588 | gvputs(job, "\n" ); |
589 | indent(job, sp->Level); |
590 | gvputs(job, "]" ); |
591 | return 1; |
592 | } |
593 | |
594 | typedef struct { |
595 | Dtlink_t link; |
596 | char* id; |
597 | int v; |
598 | } intm; |
599 | |
600 | static void freef(Dt_t * dt, intm * obj, Dtdisc_t * disc) |
601 | { |
602 | free(obj->id); |
603 | free(obj); |
604 | } |
605 | |
606 | static Dtdisc_t intDisc = { |
607 | offsetof(intm, id), |
608 | -1, |
609 | offsetof(intm, link), |
610 | (Dtmake_f) NULL, |
611 | (Dtfree_f) freef, |
612 | (Dtcompar_f) NULL, |
613 | 0, |
614 | 0, |
615 | 0 |
616 | }; |
617 | |
618 | #define NEW(t) (t*)calloc(1,sizeof(t)) |
619 | |
620 | static int lookup (Dt_t* map, char* name) |
621 | { |
622 | intm* ip = (intm*)dtmatch(map, name); |
623 | if (ip) return ip->v; |
624 | else return -1; |
625 | } |
626 | |
627 | static void insert (Dt_t* map, char* name, int v) |
628 | { |
629 | intm* ip = (intm*)dtmatch(map, name); |
630 | |
631 | if (ip) { |
632 | if (ip->v != v) |
633 | agerr(AGWARN, "Duplicate cluster name \"%s\"\n" , name); |
634 | return; |
635 | } |
636 | ip = NEW(intm); |
637 | ip->id = strdup(name); |
638 | ip->v = v; |
639 | dtinsert (map, ip); |
640 | } |
641 | |
642 | static int label_subgs(Agraph_t* g, int lbl, Dt_t* map) |
643 | { |
644 | Agraph_t* sg; |
645 | |
646 | if (g != agroot(g)) { |
647 | GD_gid(g) = lbl++; |
648 | if (IS_CLUSTER(g)) |
649 | insert (map, agnameof(g), GD_gid(g)); |
650 | } |
651 | for (sg = agfstsubg(g); sg; sg = agnxtsubg(sg)) { |
652 | lbl = label_subgs(sg, lbl, map); |
653 | } |
654 | return lbl; |
655 | } |
656 | |
657 | |
658 | static void write_graph(Agraph_t * g, GVJ_t * job, int top, state_t* sp) |
659 | { |
660 | Agnode_t* np; |
661 | Agedge_t* ep; |
662 | int ncnt = 0; |
663 | int ecnt = 0; |
664 | int sgcnt = 0; |
665 | int has_subgs; |
666 | Dt_t* map; |
667 | |
668 | if (top) { |
669 | map = dtopen (&intDisc, Dtoset); |
670 | aginit(g, AGNODE, ID, sizeof(gvid_t), FALSE); |
671 | aginit(g, AGEDGE, ID, sizeof(gvid_t), FALSE); |
672 | aginit(g, AGRAPH, ID, -((int)sizeof(gvid_t)), FALSE); |
673 | sgcnt = label_subgs(g, sgcnt, map); |
674 | for (np = agfstnode(g); np; np = agnxtnode(g,np)) { |
675 | if (IS_CLUST_NODE(np)) { |
676 | ND_gid(np) = lookup(map, agnameof(np)); |
677 | } |
678 | else { |
679 | ND_gid(np) = sgcnt + ncnt++; |
680 | } |
681 | for (ep = agfstout(g, np); ep; ep = agnxtout(g,ep)) { |
682 | ED_gid(ep) = ecnt++; |
683 | } |
684 | } |
685 | dtclose(map); |
686 | } |
687 | |
688 | indent(job, sp->Level++); |
689 | gvputs(job, "{\n" ); |
690 | write_hdr(g, job, top, sp); |
691 | write_attrs((Agobj_t*)g, job, sp); |
692 | if (top) { |
693 | gvputs(job, ",\n" ); |
694 | indent(job, sp->Level); |
695 | gvprintf(job, "\"_subgraph_cnt\": %d" , sgcnt); |
696 | } else { |
697 | gvputs(job, ",\n" ); |
698 | indent(job, sp->Level); |
699 | gvprintf(job, "\"_gvid\": %d" , GD_gid(g)); |
700 | } |
701 | has_subgs = write_subgs(g, job, top, sp); |
702 | write_nodes (g, job, top, has_subgs, sp); |
703 | write_edges (g, job, top, sp); |
704 | gvputs(job, "\n" ); |
705 | sp->Level--; |
706 | indent(job, sp->Level); |
707 | if (top) |
708 | gvputs(job, "}\n" ); |
709 | else |
710 | gvputs(job, "}" ); |
711 | } |
712 | |
713 | typedef int (*putstrfn) (void *chan, const char *str); |
714 | typedef int (*flushfn) (void *chan); |
715 | |
716 | static void json_end_graph(GVJ_t *job) |
717 | { |
718 | graph_t *g = job->obj->u.g; |
719 | state_t sp; |
720 | Agiodisc_t* io_save; |
721 | static Agiodisc_t io; |
722 | |
723 | if (io.afread == NULL) { |
724 | io.afread = AgIoDisc.afread; |
725 | io.putstr = (putstrfn)gvputs; |
726 | io.flush = (flushfn)gvflush; |
727 | } |
728 | |
729 | io_save = g->clos->disc.io; |
730 | g->clos->disc.io = &io; |
731 | |
732 | set_attrwf(g, TRUE, FALSE); |
733 | sp.Level = 0; |
734 | sp.isLatin = (GD_charset(g) == CHAR_LATIN1); |
735 | sp.doXDot = ((job->render.id == FORMAT_JSON) || (job->render.id == FORMAT_XDOT_JSON)); |
736 | sp.Attrs_not_written_flag = 0; |
737 | write_graph(g, job, TRUE, &sp); |
738 | /* agwrite(g, (FILE*)job); */ |
739 | } |
740 | |
741 | gvrender_engine_t json_engine = { |
742 | 0, /* json_begin_job */ |
743 | 0, /* json_end_job */ |
744 | json_begin_graph, |
745 | json_end_graph, |
746 | 0, /* json_begin_layer */ |
747 | 0, /* json_end_layer */ |
748 | 0, /* json_begin_page */ |
749 | 0, /* json_end_page */ |
750 | 0, /* json_begin_cluster */ |
751 | 0, /* json_end_cluster */ |
752 | 0, /* json_begin_nodes */ |
753 | 0, /* json_end_nodes */ |
754 | 0, /* json_begin_edges */ |
755 | 0, /* json_end_edges */ |
756 | 0, /* json_begin_node */ |
757 | 0, /* json_end_node */ |
758 | 0, /* json_begin_edge */ |
759 | 0, /* json_end_edge */ |
760 | 0, /* json_begin_anchor */ |
761 | 0, /* json_end_anchor */ |
762 | 0, /* json_begin_label */ |
763 | 0, /* json_end_label */ |
764 | 0, /* json_textspan */ |
765 | 0, /* json_resolve_color */ |
766 | 0, /* json_ellipse */ |
767 | 0, /* json_polygon */ |
768 | 0, /* json_bezier */ |
769 | 0, /* json_polyline */ |
770 | 0, /* json_comment */ |
771 | 0, /* json_library_shape */ |
772 | }; |
773 | |
774 | gvrender_features_t render_features_json1 = { |
775 | GVRENDER_DOES_TRANSFORM, /* not really - uses raw graph coords */ /* flags */ |
776 | 0., /* default pad - graph units */ |
777 | NULL, /* knowncolors */ |
778 | 0, /* sizeof knowncolors */ |
779 | COLOR_STRING, /* color_type */ |
780 | }; |
781 | |
782 | gvrender_features_t render_features_json = { |
783 | GVRENDER_DOES_TRANSFORM /* not really - uses raw graph coords */ |
784 | | GVRENDER_DOES_MAPS |
785 | | GVRENDER_DOES_TARGETS |
786 | | GVRENDER_DOES_TOOLTIPS, /* flags */ |
787 | 0., /* default pad - graph units */ |
788 | NULL, /* knowncolors */ |
789 | 0, /* sizeof knowncolors */ |
790 | COLOR_STRING, /* color_type */ |
791 | }; |
792 | |
793 | gvdevice_features_t device_features_json_nop = { |
794 | LAYOUT_NOT_REQUIRED, /* flags */ |
795 | {0.,0.}, /* default margin - points */ |
796 | {0.,0.}, /* default page width, height - points */ |
797 | {72.,72.}, /* default dpi */ |
798 | }; |
799 | |
800 | gvdevice_features_t device_features_json = { |
801 | 0, /* flags */ |
802 | {0.,0.}, /* default margin - points */ |
803 | {0.,0.}, /* default page width, height - points */ |
804 | {72.,72.}, /* default dpi */ |
805 | }; |
806 | |
807 | gvplugin_installed_t gvrender_json_types[] = { |
808 | {FORMAT_JSON, "json" , 1, &json_engine, &render_features_json}, |
809 | {FORMAT_JSON0, "json0" , 1, &json_engine, &render_features_json}, |
810 | {FORMAT_DOT_JSON, "dot_json" , 1, &json_engine, &render_features_json}, |
811 | {FORMAT_XDOT_JSON, "xdot_json" , 1, &json_engine, &render_features_json}, |
812 | {0, NULL, 0, NULL, NULL} |
813 | }; |
814 | |
815 | gvplugin_installed_t gvdevice_json_types[] = { |
816 | {FORMAT_JSON, "json:json" , 1, NULL, &device_features_json}, |
817 | {FORMAT_JSON0, "json0:json" , 1, NULL, &device_features_json}, |
818 | {FORMAT_DOT_JSON, "dot_json:json" , 1, NULL, &device_features_json_nop}, |
819 | {FORMAT_XDOT_JSON, "xdot_json:json" , 1, NULL, &device_features_json_nop}, |
820 | {0, NULL, 0, NULL, NULL} |
821 | }; |
822 | |