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