Graphviz 16.1.1~dev.20261104.1411
Loading...
Searching...
No Matches
graphml2gv.c
Go to the documentation of this file.
1
7/*************************************************************************
8 * Copyright (c) 2011 AT&T Intellectual Property
9 * All rights reserved. This program and the accompanying materials
10 * are made available under the terms of the Eclipse Public License v2.0
11 * which accompanies this distribution, and is available at
12 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
13 *
14 * Contributors: Details at https://graphviz.org
15 *************************************************************************/
16
17#include "config.h"
18
19#include "convert.h"
20#include "openFile.h"
21#include <assert.h>
22#include <getopt.h>
23#include <inttypes.h>
24#include <limits.h>
25#include <stdbool.h>
26#include <stdint.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <util/agxbuf.h>
31#include <util/alloc.h>
32#include <util/debug.h>
33#include <util/exit.h>
34#include <util/gv_ctype.h>
35#include <util/list.h>
36#include <util/unreachable.h>
37#ifdef HAVE_EXPAT
38#include <expat.h>
39
40#define NAMEBUF 100
41
42#define GRAPHML_ID "_graphml_id"
43
44#define TAG_NONE -1
45#define TAG_GRAPH 0
46#define TAG_NODE 1
47#define TAG_EDGE 2
48
49static FILE *outFile;
50static char *CmdName;
51static char **Files;
52static int Verbose;
53static char *gname = "";
54
55typedef LIST(char *) strs_t;
56
57static void pushString(strs_t *stk, const char *s) {
58
59 // duplicate the string we will push
60 char *copy = gv_strdup(s);
61
62 // push this onto the stack
63 LIST_PUSH_BACK(stk, copy);
64}
65
66static void popString(strs_t *stk) {
67
68 if (LIST_IS_EMPTY(stk)) {
69 fprintf(stderr, "PANIC: graphml2gv: empty element stack\n");
70 graphviz_exit(EXIT_FAILURE);
71 }
72
73 LIST_DROP_BACK(stk);
74}
75
76static char *topString(strs_t *stk) {
77
78 if (LIST_IS_EMPTY(stk)) {
79 fprintf(stderr, "PANIC: graphml2gv: empty element stack\n");
80 graphviz_exit(EXIT_FAILURE);
81 }
82
83 return *LIST_BACK(stk);
84}
85
86static void freeString(strs_t *stk) { LIST_FREE(stk); }
87
88typedef struct {
89 char *gname;
90 strs_t elements;
91 int closedElementType;
92 bool edgeinverted;
93} userdata_t;
94
95static Agraph_t *root; /* root graph */
96static Agraph_t *G; /* Current graph */
97static Agedge_t *E; // current edge
98
99static LIST(Agraph_t *) Gstack;
100
101static userdata_t genUserdata(char *dfltname) {
102 userdata_t user = {0};
103 user.elements = (strs_t){.dtor = LIST_DTOR_FREE};
104 user.closedElementType = TAG_NONE;
105 user.edgeinverted = false;
106 user.gname = dfltname;
107 return user;
108}
109
110static void freeUserdata(userdata_t ud) { freeString(&ud.elements); }
111
112static int isAnonGraph(const char *name) {
113 if (*name++ != '%')
114 return 0;
115 while (gv_isdigit(*name))
116 name++; /* skip over digits */
117 return (*name == '\0');
118}
119
120static void push_subg(Agraph_t *g) {
121 // save the root if this is the first graph
122 if (LIST_IS_EMPTY(&Gstack)) {
123 root = g;
124 }
125
126 // insert the new graph
127 LIST_PUSH_BACK(&Gstack, g);
128
129 // update the top graph
130 G = g;
131}
132
133static Agraph_t *pop_subg(void) {
134 if (LIST_IS_EMPTY(&Gstack)) {
135 fprintf(stderr, "graphml2gv: Gstack underflow in graph parser\n");
136 graphviz_exit(EXIT_FAILURE);
137 }
138
139 // pop the top graph
140 Agraph_t *g = LIST_POP_BACK(&Gstack);
141
142 // update the top graph
143 if (!LIST_IS_EMPTY(&Gstack)) {
144 G = *LIST_BACK(&Gstack);
145 }
146
147 return g;
148}
149
150static Agnode_t *bind_node(const char *name) {
151 return agnode(G, (char *)name, 1);
152}
153
154static Agedge_t *bind_edge(const char *tail, const char *head) {
155 Agnode_t *tailNode, *headNode;
156 char *key = 0;
157
158 tailNode = agnode(G, (char *)tail, 1);
159 headNode = agnode(G, (char *)head, 1);
160 E = agedge(G, tailNode, headNode, key, 1);
161 return E;
162}
163
164static int get_xml_attr(char *attrname, const char **atts) {
165 int count = 0;
166 while (atts[count] != NULL) {
167 if (strcmp(attrname, atts[count]) == 0) {
168 return count + 1;
169 }
170 count += 2;
171 }
172 return -1;
173}
174
175static char *defval = "";
176
177static void setEdgeAttr(Agedge_t *ep, char *name, const char *value,
178 userdata_t *ud) {
179 if (strcmp(name, "headport") == 0) {
180 char *const attrname = ud->edgeinverted ? "tailport" : "headport";
181 Agsym_t *ap = agattr_text(root, AGEDGE, attrname, 0);
182 if (!ap)
183 ap = agattr_text(root, AGEDGE, attrname, defval);
184 agxset(ep, ap, value);
185 } else if (strcmp(name, "tailport") == 0) {
186 char *const attrname = ud->edgeinverted ? "headport" : "tailport";
187 Agsym_t *ap = agattr_text(root, AGEDGE, attrname, 0);
188 if (!ap)
189 ap = agattr_text(root, AGEDGE, attrname, defval);
190 agxset(ep, ap, value);
191 } else {
192 Agsym_t *ap = agattr_text(root, AGEDGE, name, 0);
193 if (!ap)
194 ap = agattr_text(root, AGEDGE, name, defval);
195 agxset(ep, ap, value);
196 }
197}
198
199/*------------- expat handlers ----------------------------------*/
200
201static void startElementHandler(void *userData, const char *name,
202 const char **atts) {
203 int pos;
204 userdata_t *ud = userData;
205 Agraph_t *g = NULL;
206
207 if (strcmp(name, "graphml") == 0) {
208 /* do nothing */
209 } else if (strcmp(name, "graph") == 0) {
210 const char *edgeMode = "";
211 const char *id;
212 Agdesc_t dir;
213 char buf[NAMEBUF]; /* holds % + number */
214
215 if (ud->closedElementType == TAG_GRAPH) {
216 fprintf(stderr, "Warning: Node contains more than one graph.\n");
217 }
218 pos = get_xml_attr("id", atts);
219 if (pos > 0) {
220 id = atts[pos];
221 } else
222 id = ud->gname;
223 pos = get_xml_attr("edgedefault", atts);
224 if (pos > 0) {
225 edgeMode = atts[pos];
226 }
227
228 if (LIST_IS_EMPTY(&Gstack)) {
229 if (strcmp(edgeMode, "directed") == 0) {
230 dir = Agdirected;
231 } else if (strcmp(edgeMode, "undirected") == 0) {
232 dir = Agundirected;
233 } else {
234 GV_INFO(
235 "Warning: graph has no edgedefault attribute - assume directed");
236 dir = Agdirected;
237 }
238 g = agopen((char *)id, dir, &AgDefaultDisc);
239 push_subg(g);
240 } else {
241 Agraph_t *subg;
242 if (isAnonGraph(id)) {
243 static int anon_id = 1;
244 snprintf(buf, sizeof(buf), "%%%d", anon_id++);
245 id = buf;
246 }
247 subg = agsubg(G, (char *)id, 1);
248 push_subg(subg);
249 }
250
251 pushString(&ud->elements, id);
252 } else if (strcmp(name, "node") == 0) {
253 pos = get_xml_attr("id", atts);
254 if (pos > 0) {
255 const char *attrname;
256 attrname = atts[pos];
257 if (G == 0)
258 fprintf(stderr, "node %s outside graph, ignored\n", attrname);
259 else
260 bind_node(attrname);
261
262 pushString(&ud->elements, attrname);
263 }
264
265 } else if (strcmp(name, "edge") == 0) {
266 const char *head = "", *tail = "";
267 char *tname;
268 Agnode_t *t;
269
270 pos = get_xml_attr("source", atts);
271 if (pos > 0)
272 tail = atts[pos];
273 pos = get_xml_attr("target", atts);
274 if (pos > 0)
275 head = atts[pos];
276
277 if (G == 0)
278 fprintf(stderr, "edge source %s target %s outside graph, ignored\n", tail,
279 head);
280 else {
281 bind_edge(tail, head);
282
283 t = AGTAIL(E);
284 tname = agnameof(t);
285
286 if (strcmp(tname, tail) == 0) {
287 ud->edgeinverted = false;
288 } else if (strcmp(tname, head) == 0) {
289 ud->edgeinverted = true;
290 }
291
292 pos = get_xml_attr("id", atts);
293 if (pos > 0) {
294 setEdgeAttr(E, GRAPHML_ID, atts[pos], ud);
295 }
296 }
297 } else {
298 /* must be some extension */
299 fprintf(stderr, "Unknown node %s - ignoring.\n", name);
300 }
301}
302
303static void endElementHandler(void *userData, const char *name) {
304 userdata_t *ud = userData;
305
306 if (strcmp(name, "graph") == 0) {
307 pop_subg();
308 popString(&ud->elements);
309 ud->closedElementType = TAG_GRAPH;
310 } else if (strcmp(name, "node") == 0) {
311 char *ele_name = topString(&ud->elements);
312 if (ud->closedElementType == TAG_GRAPH) {
313 Agnode_t *node = agnode(root, ele_name, 0);
314 if (node)
315 agdelete(root, node);
316 }
317 popString(&ud->elements);
318 ud->closedElementType = TAG_NODE;
319 } else if (strcmp(name, "edge") == 0) {
320 E = 0;
321 ud->closedElementType = TAG_EDGE;
322 ud->edgeinverted = false;
323 }
324}
325
326static uint64_t expat_get_lineno(XML_Parser parser) {
327 // Expat 2.9.0 introduced `XML_GetCurrentLineNumber64` and deprecated
328 // `XML_GetCurrentLineNumber`
329#if XML_MAJOR_VERSION > 2 || (XML_MAJOR_VERSION == 2 && XML_MINOR_VERSION >= 9)
330 return XML_GetCurrentLineNumber64(parser);
331#else
332 return XML_GetCurrentLineNumber(parser);
333#endif
334}
335
336static Agraph_t *graphml_to_gv(char *graphname, FILE *graphmlFile, int *rv) {
337 char buf[BUFSIZ];
338 int done;
339 userdata_t udata = genUserdata(graphname);
340 XML_Parser parser = XML_ParserCreate(NULL);
341
342 *rv = 0;
343 XML_SetUserData(parser, &udata);
344 XML_SetElementHandler(parser, startElementHandler, endElementHandler);
345
346 root = 0;
347 do {
348 size_t len = fread(buf, 1, sizeof(buf), graphmlFile);
349 if (len == 0)
350 break;
351 done = len < sizeof(buf);
352 assert(len <= INT_MAX);
353 if (XML_Parse(parser, buf, (int)len, done) == XML_STATUS_ERROR) {
354 fprintf(stderr, "%s at line %" PRIu64 "\n",
355 XML_ErrorString(XML_GetErrorCode(parser)),
356 expat_get_lineno(parser));
357 *rv = 1;
358 break;
359 }
360 } while (!done);
361 XML_ParserFree(parser);
362 freeUserdata(udata);
363
364 return root;
365}
366
367static FILE *getFile(void) {
368 FILE *rv = NULL;
369 static FILE *savef = NULL;
370 static int cnt = 0;
371
372 if (Files == NULL) {
373 if (cnt++ == 0) {
374 rv = stdin;
375 }
376 } else {
377 if (savef)
378 fclose(savef);
379 while (Files[cnt]) {
380 if ((rv = fopen(Files[cnt++], "r")) != 0)
381 break;
382 else
383 fprintf(stderr, "Can't open %s\n", Files[cnt - 1]);
384 }
385 }
386 savef = rv;
387 return rv;
388}
389
390static const char *use = "Usage: %s [-gd?] [-o<file>] [<graphs>]\n\
391 -g<name> : use <name> as template for graph names\n\
392 -o<file> : output to <file> (stdout)\n\
393 -v : verbose mode\n\
394 -? : usage\n";
395
396static void usage(int v) {
397 fprintf(stderr, use, CmdName);
398 graphviz_exit(v);
399}
400
401static char *cmdName(char *path) {
402 char *sp;
403
404 sp = strrchr(path, '/');
405 if (sp)
406 sp++;
407 else
408 sp = path;
409 return sp;
410}
411
412static void initargs(int argc, char **argv) {
413 int c;
414
415 CmdName = cmdName(argv[0]);
416 opterr = 0;
417 while ((c = getopt(argc, argv, ":vg:o:")) != -1) {
418 switch (c) {
419 case 'g':
420 gname = optarg;
421 break;
422 case 'v':
423 Verbose = 1;
424 break;
425 case 'o':
426 if (outFile != NULL)
427 fclose(outFile);
428 outFile = openFile(CmdName, optarg, "w");
429 break;
430 case ':':
431 fprintf(stderr, "%s: option -%c missing argument\n", CmdName, optopt);
432 usage(1);
433 break;
434 case '?':
435 if (optopt == '?')
436 usage(0);
437 else {
438 fprintf(stderr, "%s: option -%c unrecognized\n", CmdName, optopt);
439 usage(1);
440 }
441 break;
442 default:
443 UNREACHABLE();
444 }
445 }
446
447 argv += optind;
448 argc -= optind;
449
450 if (argc)
451 Files = argv;
452 if (!outFile)
453 outFile = stdout;
454}
455
456static char *nameOf(agxbuf *buf, char *name, int cnt) {
457 if (*name == '\0')
458 return name;
459 if (cnt) {
460 agxbprint(buf, "%s%d", name, cnt);
461 return agxbuse(buf);
462 } else
463 return name;
464}
465
466#endif
467
468int main(int argc, char **argv) {
470 Agraph_t *prev = 0;
471 FILE *inFile;
472 int rv = 0, gcnt = 0;
473
474#ifdef HAVE_EXPAT
475 agxbuf buf = {0};
476 initargs(argc, argv);
477 while ((inFile = getFile())) {
478 while ((graph = graphml_to_gv(nameOf(&buf, gname, gcnt), inFile, &rv))) {
479 gcnt++;
480 if (prev)
481 agclose(prev);
482 prev = graph;
483 GV_INFO("%s: %d nodes %d edges", agnameof(graph), agnnodes(graph),
484 agnedges(graph));
486 fflush(outFile);
487 }
488 }
489
490 LIST_FREE(&Gstack);
491
492 agxbfree(&buf);
493 graphviz_exit(rv);
494#else
495 fputs("graphml2gv: not configured for conversion from GXL to GV\n", stderr);
496 graphviz_exit(1);
497#endif
498}
Dynamically expanding string buffers.
static void agxbfree(agxbuf *xb)
free any malloced resources
Definition agxbuf.h:97
static int agxbprint(agxbuf *xb, const char *fmt,...)
Printf-style output to an agxbuf.
Definition agxbuf.h:252
static WUR char * agxbuse(agxbuf *xb)
Definition agxbuf.h:325
Memory allocation wrappers that exit on failure.
static char * gv_strdup(const char *original)
Definition alloc.h:101
static FILE * inFile
Definition acyclic.c:38
DOT-GXL converter API for gxl2gv.c and gv2gxl.c.
static const char * use
Definition cvtgxl.c:66
static FILE * outFile
Definition cvtgxl.c:35
static void initargs(int argc, char **argv)
Definition cvtgxl.c:130
static char * cmdName(char *path)
Definition cvtgxl.c:78
static char * CmdName
Definition cvtgxl.c:36
helpers for verbose/debug printing
#define GV_INFO(...)
Definition debug.h:15
#define head
Definition dthdr.h:15
static char ** Files
static NORETURN void graphviz_exit(int status)
Definition exit.h:23
#define E
Definition gdefs.h:6
#define G
Definition gdefs.h:7
static double len(glCompPoint p)
Definition glutils.c:138
static FILE * getFile(void)
Definition gml2gv.c:32
static char * gname
Definition gml2gv.c:27
static bool Verbose
Definition gml2gv.c:26
static char * nameOf(agxbuf *buf, char *name, int cnt)
Definition gml2gv.c:122
node NULL
Definition grammar.y:181
static int cnt(Dict_t *d, Dtlink_t **set)
Definition graph.c:204
int agnedges(Agraph_t *g)
Definition graph.c:169
int agnnodes(Agraph_t *g)
Definition graph.c:163
Agsym_t * agattr_text(Agraph_t *g, int kind, char *name, const char *value)
creates or looks up text attributes of a graph
Definition attr.c:333
int agxset(void *obj, Agsym_t *sym, const char *value)
Definition attr.c:521
Agdisc_t AgDefaultDisc
Definition graph.c:283
Agedge_t * agedge(Agraph_t *g, Agnode_t *t, Agnode_t *h, char *name, int createflag)
Definition edge.c:252
#define AGTAIL(e)
Definition cgraph.h:978
Agdesc_t Agundirected
undirected
Definition graph.c:280
int agclose(Agraph_t *g)
deletes a graph, freeing its associated storage
Definition graph.c:97
Agraph_t * agopen(char *name, Agdesc_t desc, Agdisc_t *disc)
creates a new graph with the given name and kind
Definition graph.c:44
int agwrite(Agraph_t *g, void *chan)
Return 0 on success, EOF on failure.
Definition write.c:736
Agdesc_t Agdirected
directed
Definition graph.c:278
Agnode_t * agnode(Agraph_t *g, char *name, int createflag)
Definition node.c:143
char * agnameof(void *)
returns a string descriptor for the object.
Definition id.c:145
int agdelete(Agraph_t *g, void *obj)
deletes object. Equivalent to agclose, agdelnode, and agdeledge for obj being a graph,...
Definition obj.c:22
@ AGEDGE
Definition cgraph.h:207
Agraph_t * agsubg(Agraph_t *g, char *name, int cflag)
Definition subg.c:52
static uint64_t id
Definition gv2gml.c:42
Agraph_t * graph(char *name)
Definition gv.cpp:34
replacements for ctype.h functions
static bool gv_isdigit(int c)
Definition gv_ctype.h:41
static const char * usage
Definition gvpr.c:54
$2 prev
Definition htmlparse.y:291
type-generic dynamically expanding list
#define LIST_PUSH_BACK(list,...)
Definition list.h:431
#define LIST_DTOR_FREE
Definition list.h:82
#define LIST(type)
Definition list.h:66
#define LIST_BACK(list)
Definition list.h:233
#define LIST_DROP_BACK(list)
Definition list.h:483
#define LIST_FREE(list)
Definition list.h:413
#define LIST_POP_BACK(list)
Definition list.h:467
#define LIST_IS_EMPTY(list)
Definition list.h:102
static FILE * openFile(const char *argv0, const char *name, const char *mode)
Definition openFile.h:8
graph descriptor
Definition cgraph.h:284
graph or subgraph
Definition cgraph.h:424
string attribute descriptor symbol in Agattr_s.dict
Definition cgraph.h:641
Definition types.h:81
int main()
Definition grammar.c:90
#define UNREACHABLE()
Definition unreachable.h:30
static void * copy(void *p, Dtdisc_t *disc UNUSED)
Definition write.c:773