/** * @file * @brief GRAPHML-DOT converter */ /************************************************************************* * Copyright (c) 2011 AT&T Intellectual Property * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v10.html * * Contributors: Details at https://graphviz.org *************************************************************************/ #include "convert.h" #include #include #include #include #include #include #include #include #include #include #include "openFile.h" #include #include #include #ifdef HAVE_EXPAT #include #ifndef XML_STATUS_ERROR #define XML_STATUS_ERROR 0 #endif #define NAMEBUF 100 #define GRAPHML_ID "_graphml_id" #define TAG_NONE -1 #define TAG_GRAPH 0 #define TAG_NODE 1 #define TAG_EDGE 2 static FILE *outFile; static char *CmdName; static char **Files; static int Verbose; static char* gname = ""; DEFINE_LIST_WITH_DTOR(strs, char *, free) static void pushString(strs_t *stk, const char *s) { // duplicate the string we will push char *copy = gv_strdup(s); // push this onto the stack strs_push_back(stk, copy); } static void popString(strs_t *stk) { if (strs_is_empty(stk)) { fprintf(stderr, "PANIC: graphml2gv: empty element stack\n"); graphviz_exit(EXIT_FAILURE); } strs_resize(stk, strs_size(stk) - 1, NULL); } static char *topString(strs_t *stk) { if (strs_is_empty(stk)) { fprintf(stderr, "PANIC: graphml2gv: empty element stack\n"); graphviz_exit(EXIT_FAILURE); } return *strs_back(stk); } static void freeString(strs_t *stk) { strs_free(stk); } typedef struct { char* gname; strs_t elements; int closedElementType; bool edgeinverted; } userdata_t; static Agraph_t *root; /* root graph */ static Agraph_t *G; /* Current graph */ static Agedge_t *E; // current edge DEFINE_LIST(graph_stack, Agraph_t *) static graph_stack_t Gstack; static userdata_t genUserdata(char *dfltname) { userdata_t user = {0}; user.elements = (strs_t){0}; user.closedElementType = TAG_NONE; user.edgeinverted = false; user.gname = dfltname; return user; } static void freeUserdata(userdata_t ud) { freeString(&ud.elements); } static int isAnonGraph(const char *name) { if (*name++ != '%') return 0; while (gv_isdigit(*name)) name++; /* skip over digits */ return (*name == '\0'); } static void push_subg(Agraph_t * g) { // save the root if this is the first graph if (graph_stack_is_empty(&Gstack)) { root = g; } // insert the new graph graph_stack_push_back(&Gstack, g); // update the top graph G = g; } static Agraph_t *pop_subg(void) { if (graph_stack_is_empty(&Gstack)) { fprintf(stderr, "graphml2gv: Gstack underflow in graph parser\n"); graphviz_exit(EXIT_FAILURE); } // pop the top graph Agraph_t *g = graph_stack_pop_back(&Gstack); // update the top graph if (!graph_stack_is_empty(&Gstack)) { G = *graph_stack_back(&Gstack); } return g; } static Agnode_t *bind_node(const char *name) { return agnode(G, (char *)name, 1); } static Agedge_t *bind_edge(const char *tail, const char *head) { Agnode_t *tailNode, *headNode; char *key = 0; tailNode = agnode(G, (char *) tail, 1); headNode = agnode(G, (char *) head, 1); E = agedge(G, tailNode, headNode, key, 1); return E; } static int get_xml_attr(char *attrname, const char **atts) { int count = 0; while (atts[count] != NULL) { if (strcmp(attrname, atts[count]) == 0) { return count + 1; } count += 2; } return -1; } static char *defval = ""; static void setEdgeAttr(Agedge_t *ep, char *name, const char *value, userdata_t *ud) { Agsym_t *ap; char *attrname; if (strcmp(name, "headport") == 0) { if (ud->edgeinverted) attrname = "tailport"; else attrname = "headport"; ap = agattr(root, AGEDGE, attrname, 0); if (!ap) ap = agattr(root, AGEDGE, attrname, defval); agxset(ep, ap, value); } else if (strcmp(name, "tailport") == 0) { if (ud->edgeinverted) attrname = "headport"; else attrname = "tailport"; ap = agattr(root, AGEDGE, attrname, 0); if (!ap) ap = agattr(root, AGEDGE, attrname, defval); agxset(ep, ap, value); } else { ap = agattr(root, AGEDGE, name, 0); if (!ap) ap = agattr(root, AGEDGE, name, defval); agxset(ep, ap, value); } } /*------------- expat handlers ----------------------------------*/ static void startElementHandler(void *userData, const char *name, const char **atts) { int pos; userdata_t *ud = userData; Agraph_t *g = NULL; if (strcmp(name, "graphml") == 0) { /* do nothing */ } else if (strcmp(name, "graph") == 0) { const char *edgeMode = ""; const char *id; Agdesc_t dir; char buf[NAMEBUF]; /* holds % + number */ if (ud->closedElementType == TAG_GRAPH) { fprintf(stderr, "Warning: Node contains more than one graph.\n"); } pos = get_xml_attr("id", atts); if (pos > 0) { id = atts[pos]; } else id = ud->gname; pos = get_xml_attr("edgedefault", atts); if (pos > 0) { edgeMode = atts[pos]; } if (graph_stack_is_empty(&Gstack)) { if (strcmp(edgeMode, "directed") == 0) { dir = Agdirected; } else if (strcmp(edgeMode, "undirected") == 0) { dir = Agundirected; } else { if (Verbose) { fprintf(stderr, "Warning: graph has no edgedefault attribute - assume directed\n"); } dir = Agdirected; } g = agopen((char *) id, dir, &AgDefaultDisc); push_subg(g); } else { Agraph_t *subg; if (isAnonGraph(id)) { static int anon_id = 1; snprintf(buf, sizeof(buf), "%%%d", anon_id++); id = buf; } subg = agsubg(G, (char *) id, 1); push_subg(subg); } pushString(&ud->elements, id); } else if (strcmp(name, "node") == 0) { pos = get_xml_attr("id", atts); if (pos > 0) { const char *attrname; attrname = atts[pos]; if (G == 0) fprintf(stderr,"node %s outside graph, ignored\n",attrname); else bind_node(attrname); pushString(&ud->elements, attrname); } } else if (strcmp(name, "edge") == 0) { const char *head = "", *tail = ""; char *tname; Agnode_t *t; pos = get_xml_attr("source", atts); if (pos > 0) tail = atts[pos]; pos = get_xml_attr("target", atts); if (pos > 0) head = atts[pos]; if (G == 0) fprintf(stderr,"edge source %s target %s outside graph, ignored\n",tail,head); else { bind_edge(tail, head); t = AGTAIL(E); tname = agnameof(t); if (strcmp(tname, tail) == 0) { ud->edgeinverted = false; } else if (strcmp(tname, head) == 0) { ud->edgeinverted = true; } pos = get_xml_attr("id", atts); if (pos > 0) { setEdgeAttr(E, GRAPHML_ID, atts[pos], ud); } } } else { /* must be some extension */ fprintf(stderr, "Unknown node %s - ignoring.\n", name); } } static void endElementHandler(void *userData, const char *name) { userdata_t *ud = userData; if (strcmp(name, "graph") == 0) { pop_subg(); popString(&ud->elements); ud->closedElementType = TAG_GRAPH; } else if (strcmp(name, "node") == 0) { char *ele_name = topString(&ud->elements); if (ud->closedElementType == TAG_GRAPH) { Agnode_t *node = agnode(root, ele_name, 0); if (node) agdelete(root, node); } popString(&ud->elements); ud->closedElementType = TAG_NODE; } else if (strcmp(name, "edge") == 0) { E = 0; ud->closedElementType = TAG_EDGE; ud->edgeinverted = false; } } static Agraph_t *graphml_to_gv(char *graphname, FILE *graphmlFile, int *rv) { char buf[BUFSIZ]; int done; userdata_t udata = genUserdata(graphname); XML_Parser parser = XML_ParserCreate(NULL); *rv = 0; XML_SetUserData(parser, &udata); XML_SetElementHandler(parser, startElementHandler, endElementHandler); root = 0; do { size_t len = fread(buf, 1, sizeof(buf), graphmlFile); if (len == 0) break; done = len < sizeof(buf); assert(len <= INT_MAX); if (XML_Parse(parser, buf, (int)len, done) == XML_STATUS_ERROR) { fprintf(stderr, "%s at line %lu\n", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); *rv = 1; break; } } while (!done); XML_ParserFree(parser); freeUserdata(udata); return root; } static FILE *getFile(void) { FILE *rv = NULL; static FILE *savef = NULL; static int cnt = 0; if (Files == NULL) { if (cnt++ == 0) { rv = stdin; } } else { if (savef) fclose(savef); while (Files[cnt]) { if ((rv = fopen(Files[cnt++], "r")) != 0) break; else fprintf(stderr, "Can't open %s\n", Files[cnt - 1]); } } savef = rv; return rv; } static const char *use = "Usage: %s [-gd?] [-o] []\n\ -g : use as template for graph names\n\ -o : output to (stdout)\n\ -v : verbose mode\n\ -? : usage\n"; static void usage(int v) { fprintf(stderr, use, CmdName); graphviz_exit(v); } static char *cmdName(char *path) { char *sp; sp = strrchr(path, '/'); if (sp) sp++; else sp = path; return sp; } static void initargs(int argc, char **argv) { int c; CmdName = cmdName(argv[0]); opterr = 0; while ((c = getopt(argc, argv, ":vg:o:")) != -1) { switch (c) { case 'g': gname = optarg; break; case 'v': Verbose = 1; break; case 'o': if (outFile != NULL) fclose(outFile); outFile = openFile(CmdName, optarg, "w"); break; case ':': fprintf(stderr, "%s: option -%c missing argument\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; } static char *nameOf(agxbuf *buf, char *name, int cnt) { if (*name == '\0') return name; if (cnt) { agxbprint(buf, "%s%d", name, cnt); return agxbuse(buf); } else return name; } #endif int main(int argc, char **argv) { Agraph_t *graph; Agraph_t *prev = 0; FILE *inFile; int rv = 0, gcnt = 0; #ifdef HAVE_EXPAT agxbuf buf = {0}; initargs(argc, argv); while ((inFile = getFile())) { while ((graph = graphml_to_gv(nameOf(&buf, gname, gcnt), inFile, &rv))) { gcnt++; if (prev) agclose(prev); prev = graph; if (Verbose) fprintf (stderr, "%s: %d nodes %d edges\n", agnameof(graph), agnnodes(graph), agnedges(graph)); agwrite(graph, outFile); fflush(outFile); } } graph_stack_free(&Gstack); agxbfree(&buf); graphviz_exit(rv); #else fputs("graphml2gv: not configured for conversion from GXL to GV\n", stderr); graphviz_exit(1); #endif }