/** * @file * @brief DOT-GML converter */ /********************************************************** * This software is part of the graphviz package * * https://graphviz.org * * * * Copyright (c) 1994-2004 AT&T Corp. * * and is licensed under the * * Common Public License, Version 1.0 * * by AT&T Corp. * * * * Information and Software Systems Research * * AT&T Research, Florham Park NJ * **********************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "openFile.h" static FILE *outFile; static char *CmdName; static char **Files; static uint64_t id; static bool yworks; ///< use yWorks.com variant of GML? #define POS_SET (1<<0) #define W_SET (1<<1) #define H_SET (1<<2) #define INVIS (1<<3) #define FILL (1<<4) #define LINE (1<<5) #define DASH (1<<6) #define DOT (1<<7) #define BOLD (1<<8) typedef struct { unsigned int flags; /* graphics */ double x; double y; double w; double h; char* type; /* shape */ char* image; char* fill; /* fillcolor */ char* outline; /* pencolor */ char* width; /* penwidth */ char* outlineStyle; /* label graphics */ char* fontColor; char* fontSize; char* fontName; } node_attrs; typedef struct { unsigned int flags; /* graphics */ char* width; /* penwidth */ char* fill; /* pencolor */ char* arrow; /* dir */ char* arrowhead; /* arrowhead */ char* arrowtail; /* arrowtail */ char* pos; /* label graphics */ char* fontColor; char* fontSize; char* fontName; } edge_attrs; typedef struct { Agrec_t h; uint64_t id; } Local_Agnodeinfo_t; #define ID(n) (((Local_Agnodeinfo_t*)(n->base.data))->id) static void indent(int ix) { while (ix--) fprintf (outFile, " "); } /// Return true if input string is number static bool isNumber(char* s) { char* ep = s; strtod(s, &ep); if (s != ep) { while (gv_isspace(*ep)) ep++; if (*ep) return false; return true; } return false; } /// Simple implementation for parsing style attribute static int parseStyle (char* s) { int flags = 0; char* sep = " \t,"; for (tok_t t = tok(s, sep); !tok_end(&t); tok_next(&t)) { strview_t ip = tok_get(&t); if (strview_str_eq(ip, "invis")) flags |= INVIS; else if (strview_str_eq(ip, "filled")) flags |= FILL; else if (strview_str_eq(ip, "dashed")) flags |= DASH; else if (strview_str_eq(ip, "dotted")) flags |= DOT; else if (strview_str_eq(ip, "solid")) flags |= LINE; else if (strview_str_eq(ip, "bold")) flags |= BOLD; } return flags; } static void emitInt(char *name, int value, int ix) { indent(ix); fprintf (outFile, "%s %d\n", name, value); } static void emitReal(char *name, double value, int ix) { indent(ix); fprintf (outFile, "%s %g\n", name, value); } static void emitPoint(double x, double y, int ix) { indent(ix); fprintf (outFile, "point [ x %g y %g ]\n", x, y); } static char* skipWS (char* s) { while (gv_isspace(*s)) s++; return s; } /* Return NULL if unsuccessful */ static char* readPoint (char* s, double* xp, double* yp) { char* endp; s = skipWS(s); *xp = strtod (s, &endp); if (s == endp) return NULL; endp++; /* skip comma */ s = endp; *yp = strtod (s, &endp); if (s == endp) return NULL; else return endp; } static char* arrowEnd (char* s0, char* pfx, int* fp, double* xp, double* yp) { char* s = skipWS(s0); if (strncmp(s,pfx,2)) return s; s += 2; /* skip prefix */ s = readPoint (s, xp, yp); if (s == NULL) { fprintf (stderr, "Illegal spline end: %s\n", s0); graphviz_exit(1); } *fp = 1; return s; } static void emitSpline(char *s, int ix) { double sx, sy, ex, ey; int sarrow = 0; int earrow = 0; s = arrowEnd (s, "e,", &earrow, &ex, &ey); s = arrowEnd (s, "s,", &sarrow, &sx, &sy); indent(ix); fprintf (outFile, "Line [\n"); if (sarrow) emitPoint(sx, sy, ix+1); while ((s = readPoint (s, &sx, &sy))) emitPoint(sx, sy, ix+1); if (earrow) emitPoint(ex, ey, ix+1); indent(ix); fprintf (outFile, "]\n"); } // `fputs` wrapper to handle the difference in calling convention to what // `xml_escape`’s `cb` expects static inline int put(void *stream, const char *s) { return fputs(s, stream); } // write a string to the given file, XML-escaping the input static inline int xml_puts(FILE *stream, const char *s) { const xml_flags_t flags = {.dash = 1, .nbsp = 1}; return xml_escape(s, flags, put, stream); } static void emitAttr(char *name, char *value, int ix) { indent(ix); if (isNumber (value)) fprintf (outFile, "%s %s\n", name, value); else { fprintf(outFile, "%s \"", name); xml_puts(outFile, value); fputs("\"\n", outFile); } } /* node attributes: * label * graphics * LabelGraphics */ static void emitNodeAttrs(Agraph_t *G, Agnode_t *np, int ix) { Agsym_t* s; char* v; node_attrs attrs; int doGraphics = 0; int doLabelGraphics = 0; char* label = 0; int style; double x, y; /* First, process the attributes, saving the graphics attributes */ memset(&attrs,0, sizeof(attrs)); for (s = agnxtattr (G, AGNODE, NULL); s; s = agnxtattr (G, AGNODE, s)) { if (streq(s->name, "style")) { /* hasFill outlineStyle invis */ if (*(v = agxget (np, s))) { style = parseStyle (v); if (style & INVIS) attrs.flags |= INVIS; if (style & FILL) attrs.flags |= FILL; if (style & LINE) attrs.outlineStyle = "line"; if (style & DASH) attrs.outlineStyle = "dashed"; if (style & DOT) attrs.outlineStyle = "dotted"; doGraphics = 1; } } else if (streq(s->name, "label")) { v = agxget (np, s); if (streq("\\N", v)) { label = agnameof(np); emitAttr(s->name, label, ix); doLabelGraphics = 1; } else if (*v) { label = v; emitAttr(s->name, label, ix); doLabelGraphics = 1; } } else if (streq(s->name, "penwidth")) { if (*(v = agxget (np, s))) { attrs.width = v; doGraphics = 1; } } else if (streq(s->name, "width")) { v = agxget (np, s); if (*v) { attrs.w = 72.0*atof (v); attrs.flags |= W_SET; doGraphics = 1; } } else if (streq(s->name, "height")) { v = agxget (np, s); if (*v) { attrs.h = 72.0*atof (v); attrs.flags |= H_SET; doGraphics = 1; } } else if (streq(s->name, "pos")) { v = agxget (np, s); if (sscanf (v, "%lf,%lf", &x, &y) == 2) { doGraphics = 1; attrs.x = x; attrs.y = y; attrs.flags |= POS_SET; } } else if (streq(s->name, "shape")) { /* type */ if (*(v = agxget (np, s))) { attrs.type = v; doGraphics = 1; } } else if (streq(s->name, "color")) { if (*(v = agxget (np, s))) { attrs.fill = v; attrs.outline = v; doGraphics = 1; } } else if (streq(s->name, "fillcolor")) { if (*(v = agxget (np, s))) { attrs.fill = v; doGraphics = 1; } } else if (streq(s->name, "pencolor")) { if (*(v = agxget (np, s))) { attrs.outline = v; doGraphics = 1; } } else if (streq(s->name, "fontname")) { /* fontName */ if (*(v = agxget (np, s))) { attrs.fontName = v; doLabelGraphics = 1; } } else if (streq(s->name, "fontsize")) { /* fontSize */ if (*(v = agxget (np, s))) { attrs.fontSize = v; doLabelGraphics = 1; } } else if (streq(s->name, "fontcolor")) { /* fontColor */ if (*(v = agxget (np, s))) { attrs.fontColor = v; doLabelGraphics = 1; } } else { v = agxget (np, s); emitAttr(s->name, v, ix); } } /* Then, print them, if any */ if (doGraphics) { fprintf (outFile, " graphics [\n"); if (attrs.flags & POS_SET) { emitReal("x", attrs.x, ix+1); emitReal("y", attrs.y, ix+1); } if (attrs.flags & W_SET) { emitReal("w", attrs.w, ix+1); } if (attrs.flags & H_SET) { emitReal("H", attrs.h, ix+1); } if (attrs.flags & INVIS) { emitInt("visible", 0, ix+1); } if (attrs.flags & FILL) { emitInt("hasFill", 1, ix+1); } if (attrs.type) { emitAttr("type", attrs.type, ix+1); } if (attrs.image) { emitAttr("image", attrs.image, ix+1); } if (attrs.fill) { emitAttr("fill", attrs.fill, ix+1); } if (attrs.outline) { emitAttr("outline", attrs.outline, ix+1); } if (attrs.width) { emitAttr("width", attrs.width, ix+1); } if (attrs.outlineStyle) { emitAttr("outlineStyle", attrs.outlineStyle, ix+1); } fprintf (outFile, " ]\n"); } if (doLabelGraphics) { fprintf (outFile, " LabelGraphics [\n"); if (label) emitAttr("text", label, ix+1); if (attrs.fontColor) { emitAttr(yworks ? "color" : "fontColor", attrs.fontColor, ix+1); } if (attrs.fontSize) { emitAttr("fontSize", attrs.fontSize, ix+1); } if (attrs.fontName) { emitAttr("fontName", attrs.fontName, ix+1); } fprintf (outFile, " ]\n"); } } /* emitNode: * set node id * label, width height, x, y, type fillcolor */ static void emitNode(Agraph_t *G, Agnode_t *n) { agbindrec(n, "nodeinfo", sizeof(Local_Agnodeinfo_t), true); fprintf(outFile, " node [\n id %" PRIu64 "\n name \"%s\"\n", id, agnameof(n)); ID(n) = id++; emitNodeAttrs(G, n, 2); fprintf (outFile, " ]\n"); } /* edge attributes: * label * graphics * LabelGraphics */ static void emitEdgeAttrs(Agraph_t *G, Agedge_t *ep, int ix) { Agsym_t* s; char* v; edge_attrs attrs; int doGraphics = 0; int doLabelGraphics = 0; char* label = 0; int style; /* First, process the attributes, saving the graphics attributes */ memset(&attrs,0, sizeof(attrs)); for (s = agnxtattr (G, AGEDGE, NULL); s; s = agnxtattr (G, AGEDGE, s)) { if (streq(s->name, "style")) { /* hasFill outlineStyle invis */ if (*(v = agxget (ep, s))) { style = parseStyle (v); if (style & INVIS) attrs.flags |= INVIS; if (style & LINE) attrs.flags |= LINE; if (style & DASH) attrs.flags |= DASH; if (style & DOT) attrs.flags |= DOT; if (style & BOLD) attrs.width = "2"; doGraphics = 1; } } else if (streq(s->name, "label")) { if (*(v = agxget (ep, s))) { label = v; emitAttr(s->name, label, ix); doLabelGraphics = 1; } } else if (streq(s->name, "penwidth")) { if (*(v = agxget (ep, s))) { attrs.width = v; doGraphics = 1; } } else if (streq(s->name, "pos")) { if (*(v = agxget (ep, s))) { doGraphics = 1; attrs.pos = v; } } else if (streq(s->name, "dir")) { if (*(v = agxget (ep, s))) { doGraphics = 1; attrs.arrow = v; } } else if (streq(s->name, "color")) { if (*(v = agxget (ep, s))) { attrs.fill = v; doGraphics = 1; } } else if (streq(s->name, "pencolor")) { if (*(v = agxget (ep, s))) { attrs.fill = v; doGraphics = 1; } } else if (streq(s->name, "arrowhead")) { if (*(v = agxget (ep, s))) { attrs.arrowhead = v; doGraphics = 1; } } else if (streq(s->name, "arrowtail")) { if (*(v = agxget (ep, s))) { attrs.arrowtail = v; doGraphics = 1; } } else if (streq(s->name, "fontname")) { /* fontName */ if (*(v = agxget (ep, s))) { attrs.fontName = v; doLabelGraphics = 1; } } else if (streq(s->name, "fontsize")) { /* fontSize */ if (*(v = agxget (ep, s))) { attrs.fontSize = v; doLabelGraphics = 1; } } else if (streq(s->name, "fontcolor")) { /* fontColor */ if (*(v = agxget (ep, s))) { attrs.fontColor = v; doLabelGraphics = 1; } } else { v = agxget (ep, s); emitAttr(s->name, v, ix); } } /* Then, print them, if any */ if (doGraphics) { fprintf (outFile, " graphics [\n"); if (attrs.pos) { emitSpline(attrs.pos, ix+1); } if (attrs.flags & INVIS) { emitInt("visible", 0, ix+1); } if (attrs.fill) { emitAttr("fill", attrs.fill, ix+1); } if (attrs.width) { emitAttr("width", attrs.width, ix+1); } if (attrs.arrowhead) { emitAttr("targetArrow", attrs.arrowhead, ix+1); } if (attrs.arrowtail) { emitAttr("sourceArrow", attrs.arrowtail, ix+1); } if (attrs.flags & DASH) { emitAttr("style", "dashed", ix+1); } else if (attrs.flags & DOT) { emitAttr("style", "dotted", ix+1); } else if (attrs.flags & LINE) { emitAttr("style", "line", ix+1); } if (attrs.arrow) { if (streq(attrs.arrow,"forward")) emitAttr("arrow", "first", ix+1); else if (streq(attrs.arrow,"back")) emitAttr("arrow", "last", ix+1); else if (streq(attrs.arrow,"both")) emitAttr("arrow", "both", ix+1); else if (streq(attrs.arrow,"none")) emitAttr("arrow", "none", ix+1); } fprintf (outFile, " ]\n"); } if (doLabelGraphics) { fprintf (outFile, " LabelGraphics [\n"); if (label) emitAttr("text", label, ix+1); if (attrs.fontColor) { emitAttr(yworks ? "color" : "fontColor", attrs.fontColor, ix+1); } if (attrs.fontSize) { emitAttr("fontSize", attrs.fontSize, ix+1); } if (attrs.fontName) { emitAttr("fontName", attrs.fontName, ix+1); } fprintf (outFile, " ]\n"); } } static void emitEdge(Agraph_t *G, Agedge_t *e) { fprintf(outFile, " edge [\n id %" PRIu64 "\n", (uint64_t)AGSEQ(e)); fprintf(outFile, " source %" PRIu64 "\n", ID(agtail(e))); fprintf(outFile, " target %" PRIu64 "\n", ID(aghead(e))); emitEdgeAttrs(G, e, 2); fprintf (outFile, " ]\n"); } static void emitGraphAttrs(Agraph_t *G) { Agsym_t* s; char* v; for (s = agnxtattr (G, AGRAPH, NULL); s; s = agnxtattr (G, AGRAPH, s)) { if (*(v = agxget (G, s))) { emitAttr(s->name, v, 1); } } } static void gv_to_gml(Agraph_t *G) { Agnode_t* n; Agedge_t* e; fprintf (outFile, "graph [\n version 2\n"); if (agisdirected(G)) fprintf (outFile, " directed 1\n"); else fprintf (outFile, " directed 0\n"); emitGraphAttrs(G); /* FIX: Not sure how to handle default attributes or subgraphs */ for (n = agfstnode(G); n; n = agnxtnode (G, n)) { emitNode(G, n); } for (n = agfstnode(G); n; n = agnxtnode (G, n)) { for (e = agfstout(G, n); e; e = agnxtout (G, e)) { emitEdge(G, e); } } fprintf (outFile, "]\n"); } static char *useString = "Usage: %s [-y] [-?] \n\ -o : output to (stdout)\n\ -y : output yWorks.com GML variant\n\ -? - print usage\n\ If no files are specified, stdin is used\n"; static void usage(int v) { printf(useString, CmdName); graphviz_exit(v); } static char *cmdName(char *cmd) { char *sp; sp = strrchr(cmd, '/'); if (sp) sp++; else sp = cmd; return sp; } static void initargs(int argc, char **argv) { int c; CmdName = cmdName(argv[0]); opterr = 0; while ((c = getopt(argc, argv, ":o:y")) != -1) { switch (c) { case 'o': if (outFile != NULL) fclose(outFile); outFile = openFile(CmdName, optarg, "w"); break; case 'y': yworks = true; break; case ':': fprintf(stderr, "%s: option -%c missing parameter\n", CmdName, optopt); usage(1); break; case '?': if (optopt == '?') usage(0); else { fprintf(stderr, "%s: option -%c unrecognized\n", CmdName, optopt); usage(1); } break; default: UNREACHABLE(); } } argv += optind; argc -= optind; if (argc) Files = argv; if (!outFile) outFile = stdout; } int main(int argc, char **argv) { Agraph_t *G; Agraph_t *prev = 0; int rv; ingraph_state ig; rv = 0; initargs(argc, argv); newIngraph(&ig, Files); while ((G = nextGraph(&ig))) { if (prev) { id = 0; agclose(prev); } prev = G; gv_to_gml(G); fflush(outFile); } graphviz_exit(rv); }