graphml2gv.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. /**
  2. * @file
  3. * @brief <a href=https://en.wikipedia.org/wiki/GraphML>GRAPHML</a>-DOT converter
  4. */
  5. /*************************************************************************
  6. * Copyright (c) 2011 AT&T Intellectual Property
  7. * All rights reserved. This program and the accompanying materials
  8. * are made available under the terms of the Eclipse Public License v1.0
  9. * which accompanies this distribution, and is available at
  10. * https://www.eclipse.org/legal/epl-v10.html
  11. *
  12. * Contributors: Details at https://graphviz.org
  13. *************************************************************************/
  14. #include "convert.h"
  15. #include <assert.h>
  16. #include <cgraph/gv_ctype.h>
  17. #include <cgraph/list.h>
  18. #include <getopt.h>
  19. #include <limits.h>
  20. #include <stdbool.h>
  21. #include <stdio.h>
  22. #include <stdlib.h>
  23. #include <string.h>
  24. #include "openFile.h"
  25. #include <util/agxbuf.h>
  26. #include <util/alloc.h>
  27. #include <util/exit.h>
  28. #include <util/unreachable.h>
  29. #ifdef HAVE_EXPAT
  30. #include <expat.h>
  31. #ifndef XML_STATUS_ERROR
  32. #define XML_STATUS_ERROR 0
  33. #endif
  34. #define NAMEBUF 100
  35. #define GRAPHML_ID "_graphml_id"
  36. #define TAG_NONE -1
  37. #define TAG_GRAPH 0
  38. #define TAG_NODE 1
  39. #define TAG_EDGE 2
  40. static FILE *outFile;
  41. static char *CmdName;
  42. static char **Files;
  43. static int Verbose;
  44. static char* gname = "";
  45. DEFINE_LIST_WITH_DTOR(strs, char *, free)
  46. static void pushString(strs_t *stk, const char *s) {
  47. // duplicate the string we will push
  48. char *copy = gv_strdup(s);
  49. // push this onto the stack
  50. strs_push_back(stk, copy);
  51. }
  52. static void popString(strs_t *stk) {
  53. if (strs_is_empty(stk)) {
  54. fprintf(stderr, "PANIC: graphml2gv: empty element stack\n");
  55. graphviz_exit(EXIT_FAILURE);
  56. }
  57. strs_resize(stk, strs_size(stk) - 1, NULL);
  58. }
  59. static char *topString(strs_t *stk) {
  60. if (strs_is_empty(stk)) {
  61. fprintf(stderr, "PANIC: graphml2gv: empty element stack\n");
  62. graphviz_exit(EXIT_FAILURE);
  63. }
  64. return *strs_back(stk);
  65. }
  66. static void freeString(strs_t *stk) {
  67. strs_free(stk);
  68. }
  69. typedef struct {
  70. char* gname;
  71. strs_t elements;
  72. int closedElementType;
  73. bool edgeinverted;
  74. } userdata_t;
  75. static Agraph_t *root; /* root graph */
  76. static Agraph_t *G; /* Current graph */
  77. static Agedge_t *E; // current edge
  78. DEFINE_LIST(graph_stack, Agraph_t *)
  79. static graph_stack_t Gstack;
  80. static userdata_t genUserdata(char *dfltname) {
  81. userdata_t user = {0};
  82. user.elements = (strs_t){0};
  83. user.closedElementType = TAG_NONE;
  84. user.edgeinverted = false;
  85. user.gname = dfltname;
  86. return user;
  87. }
  88. static void freeUserdata(userdata_t ud) {
  89. freeString(&ud.elements);
  90. }
  91. static int isAnonGraph(const char *name) {
  92. if (*name++ != '%')
  93. return 0;
  94. while (gv_isdigit(*name))
  95. name++; /* skip over digits */
  96. return (*name == '\0');
  97. }
  98. static void push_subg(Agraph_t * g)
  99. {
  100. // save the root if this is the first graph
  101. if (graph_stack_is_empty(&Gstack)) {
  102. root = g;
  103. }
  104. // insert the new graph
  105. graph_stack_push_back(&Gstack, g);
  106. // update the top graph
  107. G = g;
  108. }
  109. static Agraph_t *pop_subg(void)
  110. {
  111. if (graph_stack_is_empty(&Gstack)) {
  112. fprintf(stderr, "graphml2gv: Gstack underflow in graph parser\n");
  113. graphviz_exit(EXIT_FAILURE);
  114. }
  115. // pop the top graph
  116. Agraph_t *g = graph_stack_pop_back(&Gstack);
  117. // update the top graph
  118. if (!graph_stack_is_empty(&Gstack)) {
  119. G = *graph_stack_back(&Gstack);
  120. }
  121. return g;
  122. }
  123. static Agnode_t *bind_node(const char *name)
  124. {
  125. return agnode(G, (char *)name, 1);
  126. }
  127. static Agedge_t *bind_edge(const char *tail, const char *head)
  128. {
  129. Agnode_t *tailNode, *headNode;
  130. char *key = 0;
  131. tailNode = agnode(G, (char *) tail, 1);
  132. headNode = agnode(G, (char *) head, 1);
  133. E = agedge(G, tailNode, headNode, key, 1);
  134. return E;
  135. }
  136. static int get_xml_attr(char *attrname, const char **atts)
  137. {
  138. int count = 0;
  139. while (atts[count] != NULL) {
  140. if (strcmp(attrname, atts[count]) == 0) {
  141. return count + 1;
  142. }
  143. count += 2;
  144. }
  145. return -1;
  146. }
  147. static char *defval = "";
  148. static void setEdgeAttr(Agedge_t *ep, char *name, const char *value,
  149. userdata_t *ud) {
  150. Agsym_t *ap;
  151. char *attrname;
  152. if (strcmp(name, "headport") == 0) {
  153. if (ud->edgeinverted)
  154. attrname = "tailport";
  155. else
  156. attrname = "headport";
  157. ap = agattr(root, AGEDGE, attrname, 0);
  158. if (!ap)
  159. ap = agattr(root, AGEDGE, attrname, defval);
  160. agxset(ep, ap, value);
  161. } else if (strcmp(name, "tailport") == 0) {
  162. if (ud->edgeinverted)
  163. attrname = "headport";
  164. else
  165. attrname = "tailport";
  166. ap = agattr(root, AGEDGE, attrname, 0);
  167. if (!ap)
  168. ap = agattr(root, AGEDGE, attrname, defval);
  169. agxset(ep, ap, value);
  170. } else {
  171. ap = agattr(root, AGEDGE, name, 0);
  172. if (!ap)
  173. ap = agattr(root, AGEDGE, name, defval);
  174. agxset(ep, ap, value);
  175. }
  176. }
  177. /*------------- expat handlers ----------------------------------*/
  178. static void
  179. startElementHandler(void *userData, const char *name, const char **atts)
  180. {
  181. int pos;
  182. userdata_t *ud = userData;
  183. Agraph_t *g = NULL;
  184. if (strcmp(name, "graphml") == 0) {
  185. /* do nothing */
  186. } else if (strcmp(name, "graph") == 0) {
  187. const char *edgeMode = "";
  188. const char *id;
  189. Agdesc_t dir;
  190. char buf[NAMEBUF]; /* holds % + number */
  191. if (ud->closedElementType == TAG_GRAPH) {
  192. fprintf(stderr,
  193. "Warning: Node contains more than one graph.\n");
  194. }
  195. pos = get_xml_attr("id", atts);
  196. if (pos > 0) {
  197. id = atts[pos];
  198. }
  199. else
  200. id = ud->gname;
  201. pos = get_xml_attr("edgedefault", atts);
  202. if (pos > 0) {
  203. edgeMode = atts[pos];
  204. }
  205. if (graph_stack_is_empty(&Gstack)) {
  206. if (strcmp(edgeMode, "directed") == 0) {
  207. dir = Agdirected;
  208. } else if (strcmp(edgeMode, "undirected") == 0) {
  209. dir = Agundirected;
  210. } else {
  211. if (Verbose) {
  212. fprintf(stderr,
  213. "Warning: graph has no edgedefault attribute - assume directed\n");
  214. }
  215. dir = Agdirected;
  216. }
  217. g = agopen((char *) id, dir, &AgDefaultDisc);
  218. push_subg(g);
  219. } else {
  220. Agraph_t *subg;
  221. if (isAnonGraph(id)) {
  222. static int anon_id = 1;
  223. snprintf(buf, sizeof(buf), "%%%d", anon_id++);
  224. id = buf;
  225. }
  226. subg = agsubg(G, (char *) id, 1);
  227. push_subg(subg);
  228. }
  229. pushString(&ud->elements, id);
  230. } else if (strcmp(name, "node") == 0) {
  231. pos = get_xml_attr("id", atts);
  232. if (pos > 0) {
  233. const char *attrname;
  234. attrname = atts[pos];
  235. if (G == 0)
  236. fprintf(stderr,"node %s outside graph, ignored\n",attrname);
  237. else
  238. bind_node(attrname);
  239. pushString(&ud->elements, attrname);
  240. }
  241. } else if (strcmp(name, "edge") == 0) {
  242. const char *head = "", *tail = "";
  243. char *tname;
  244. Agnode_t *t;
  245. pos = get_xml_attr("source", atts);
  246. if (pos > 0)
  247. tail = atts[pos];
  248. pos = get_xml_attr("target", atts);
  249. if (pos > 0)
  250. head = atts[pos];
  251. if (G == 0)
  252. fprintf(stderr,"edge source %s target %s outside graph, ignored\n",tail,head);
  253. else {
  254. bind_edge(tail, head);
  255. t = AGTAIL(E);
  256. tname = agnameof(t);
  257. if (strcmp(tname, tail) == 0) {
  258. ud->edgeinverted = false;
  259. } else if (strcmp(tname, head) == 0) {
  260. ud->edgeinverted = true;
  261. }
  262. pos = get_xml_attr("id", atts);
  263. if (pos > 0) {
  264. setEdgeAttr(E, GRAPHML_ID, atts[pos], ud);
  265. }
  266. }
  267. } else {
  268. /* must be some extension */
  269. fprintf(stderr,
  270. "Unknown node %s - ignoring.\n",
  271. name);
  272. }
  273. }
  274. static void endElementHandler(void *userData, const char *name)
  275. {
  276. userdata_t *ud = userData;
  277. if (strcmp(name, "graph") == 0) {
  278. pop_subg();
  279. popString(&ud->elements);
  280. ud->closedElementType = TAG_GRAPH;
  281. } else if (strcmp(name, "node") == 0) {
  282. char *ele_name = topString(&ud->elements);
  283. if (ud->closedElementType == TAG_GRAPH) {
  284. Agnode_t *node = agnode(root, ele_name, 0);
  285. if (node) agdelete(root, node);
  286. }
  287. popString(&ud->elements);
  288. ud->closedElementType = TAG_NODE;
  289. } else if (strcmp(name, "edge") == 0) {
  290. E = 0;
  291. ud->closedElementType = TAG_EDGE;
  292. ud->edgeinverted = false;
  293. }
  294. }
  295. static Agraph_t *graphml_to_gv(char *graphname, FILE *graphmlFile, int *rv) {
  296. char buf[BUFSIZ];
  297. int done;
  298. userdata_t udata = genUserdata(graphname);
  299. XML_Parser parser = XML_ParserCreate(NULL);
  300. *rv = 0;
  301. XML_SetUserData(parser, &udata);
  302. XML_SetElementHandler(parser, startElementHandler, endElementHandler);
  303. root = 0;
  304. do {
  305. size_t len = fread(buf, 1, sizeof(buf), graphmlFile);
  306. if (len == 0)
  307. break;
  308. done = len < sizeof(buf);
  309. assert(len <= INT_MAX);
  310. if (XML_Parse(parser, buf, (int)len, done) == XML_STATUS_ERROR) {
  311. fprintf(stderr,
  312. "%s at line %lu\n",
  313. XML_ErrorString(XML_GetErrorCode(parser)),
  314. XML_GetCurrentLineNumber(parser));
  315. *rv = 1;
  316. break;
  317. }
  318. } while (!done);
  319. XML_ParserFree(parser);
  320. freeUserdata(udata);
  321. return root;
  322. }
  323. static FILE *getFile(void)
  324. {
  325. FILE *rv = NULL;
  326. static FILE *savef = NULL;
  327. static int cnt = 0;
  328. if (Files == NULL) {
  329. if (cnt++ == 0) {
  330. rv = stdin;
  331. }
  332. } else {
  333. if (savef)
  334. fclose(savef);
  335. while (Files[cnt]) {
  336. if ((rv = fopen(Files[cnt++], "r")) != 0)
  337. break;
  338. else
  339. fprintf(stderr, "Can't open %s\n", Files[cnt - 1]);
  340. }
  341. }
  342. savef = rv;
  343. return rv;
  344. }
  345. static const char *use = "Usage: %s [-gd?] [-o<file>] [<graphs>]\n\
  346. -g<name> : use <name> as template for graph names\n\
  347. -o<file> : output to <file> (stdout)\n\
  348. -v : verbose mode\n\
  349. -? : usage\n";
  350. static void usage(int v)
  351. {
  352. fprintf(stderr, use, CmdName);
  353. graphviz_exit(v);
  354. }
  355. static char *cmdName(char *path)
  356. {
  357. char *sp;
  358. sp = strrchr(path, '/');
  359. if (sp)
  360. sp++;
  361. else
  362. sp = path;
  363. return sp;
  364. }
  365. static void initargs(int argc, char **argv)
  366. {
  367. int c;
  368. CmdName = cmdName(argv[0]);
  369. opterr = 0;
  370. while ((c = getopt(argc, argv, ":vg:o:")) != -1) {
  371. switch (c) {
  372. case 'g':
  373. gname = optarg;
  374. break;
  375. case 'v':
  376. Verbose = 1;
  377. break;
  378. case 'o':
  379. if (outFile != NULL)
  380. fclose(outFile);
  381. outFile = openFile(CmdName, optarg, "w");
  382. break;
  383. case ':':
  384. fprintf(stderr, "%s: option -%c missing argument\n", CmdName, optopt);
  385. usage(1);
  386. break;
  387. case '?':
  388. if (optopt == '?')
  389. usage(0);
  390. else {
  391. fprintf(stderr, "%s: option -%c unrecognized\n", CmdName,
  392. optopt);
  393. usage(1);
  394. }
  395. break;
  396. default:
  397. UNREACHABLE();
  398. }
  399. }
  400. argv += optind;
  401. argc -= optind;
  402. if (argc)
  403. Files = argv;
  404. if (!outFile)
  405. outFile = stdout;
  406. }
  407. static char *nameOf(agxbuf *buf, char *name, int cnt) {
  408. if (*name == '\0')
  409. return name;
  410. if (cnt) {
  411. agxbprint(buf, "%s%d", name, cnt);
  412. return agxbuse(buf);
  413. }
  414. else
  415. return name;
  416. }
  417. #endif
  418. int main(int argc, char **argv)
  419. {
  420. Agraph_t *graph;
  421. Agraph_t *prev = 0;
  422. FILE *inFile;
  423. int rv = 0, gcnt = 0;
  424. #ifdef HAVE_EXPAT
  425. agxbuf buf = {0};
  426. initargs(argc, argv);
  427. while ((inFile = getFile())) {
  428. while ((graph = graphml_to_gv(nameOf(&buf, gname, gcnt), inFile, &rv))) {
  429. gcnt++;
  430. if (prev)
  431. agclose(prev);
  432. prev = graph;
  433. if (Verbose)
  434. fprintf (stderr, "%s: %d nodes %d edges\n",
  435. agnameof(graph), agnnodes(graph), agnedges(graph));
  436. agwrite(graph, outFile);
  437. fflush(outFile);
  438. }
  439. }
  440. graph_stack_free(&Gstack);
  441. agxbfree(&buf);
  442. graphviz_exit(rv);
  443. #else
  444. fputs("graphml2gv: not configured for conversion from GXL to GV\n", stderr);
  445. graphviz_exit(1);
  446. #endif
  447. }