1/* $Id$ $Revision$ */
2/* vim:set shiftwidth=4 ts=8: */
3
4/*************************************************************************
5 * Copyright (c) 2011 AT&T Intellectual Property
6 * All rights reserved. This program and the accompanying materials
7 * are made available under the terms of the Eclipse Public License v1.0
8 * which accompanies this distribution, and is available at
9 * http://www.eclipse.org/legal/epl-v10.html
10 *
11 * Contributors: See CVS logs. Details at http://www.graphviz.org/
12 *************************************************************************/
13
14
15#include "convert.h"
16#include <ctype.h>
17
18#define SMALLBUF 128
19
20#define NEW(t) (t*)malloc(sizeof(t))
21#define N_NEW(n,t) (t*)malloc((n)*sizeof(t))
22#define EMPTY(s) ((s == 0) || (*s == '\0'))
23#define SLEN(s) (sizeof(s)-1)
24
25#define NODE 1
26#define EDGE 2
27#define GRAPH 3
28
29#define GXL_ATTR "_gxl_"
30#define GXL_ROLE "_gxl_role"
31#define GXL_HYPER "_gxl_hypergraph"
32#define GXL_ID "_gxl_id"
33#define GXL_FROM "_gxl_fromorder"
34#define GXL_TO "_gxl_toorder"
35#define GXL_TYPE "_gxl_type"
36#define GXL_COMP "_gxl_composite_"
37#define GXL_LOC "_gxl_locator_"
38
39#define GXL_ATTR_LEN (SLEN(GXL_ATTR))
40#define GXL_COMP_LEN (SLEN(GXL_COMP))
41#define GXL_LOC_LEN (SLEN(GXL_LOC))
42
43typedef struct {
44 Agrec_t h;
45 int written;
46} Agnodeinfo_t;
47
48static int Level; /* level of tabs */
49static Agsym_t *Tailport, *Headport;
50
51typedef struct {
52 Dtlink_t link;
53 char *name;
54 char *unique_name;
55} namev_t;
56
57static namev_t *make_nitem(Dt_t * d, namev_t * objp, Dtdisc_t * disc)
58{
59 namev_t *np = NEW(namev_t);
60 np->name = objp->name;
61 np->unique_name = 0;
62 return np;
63}
64
65static void free_nitem(Dt_t * d, namev_t * np, Dtdisc_t * disc)
66{
67 free(np);
68}
69
70static Dtdisc_t nameDisc = {
71 offsetof(namev_t, name),
72 -1,
73 offsetof(namev_t, link),
74 (Dtmake_f) make_nitem,
75 (Dtfree_f) free_nitem,
76 NIL(Dtcompar_f),
77 NIL(Dthash_f),
78 NIL(Dtmemory_f),
79 NIL(Dtevent_f)
80};
81
82typedef struct {
83 Dtlink_t link;
84 char *name;
85} idv_t;
86
87static void free_iditem(Dt_t * d, idv_t * idp, Dtdisc_t * disc)
88{
89 free(idp->name);
90 free(idp);
91}
92
93static Dtdisc_t idDisc = {
94 offsetof(idv_t, name),
95 -1,
96 offsetof(idv_t, link),
97 NIL(Dtmake_f),
98 (Dtfree_f) free_iditem,
99 NIL(Dtcompar_f),
100 NIL(Dthash_f),
101 NIL(Dtmemory_f),
102 NIL(Dtevent_f)
103};
104
105typedef struct {
106 Dt_t *nodeMap;
107 Dt_t *graphMap;
108 Dt_t *synNodeMap;
109 Dt_t *idList;
110 Agraph_t *root;
111 char attrsNotWritten;
112 char directed;
113} gxlstate_t;
114
115static void writeBody(gxlstate_t *, Agraph_t * g, FILE * gxlFile);
116static void iterateBody(gxlstate_t * stp, Agraph_t * g);
117
118static void tabover(FILE * gxlFile)
119{
120 int temp;
121 temp = Level;
122 while (temp--)
123 putc('\t', gxlFile);
124}
125
126/* legalGXLName:
127 * By XML spec,
128 * ID := (alpha|'_'|':')(NameChar)*
129 * NameChar := alpha|digit|'.'|':'|'-'|'_'
130 */
131static int legalGXLName(char *id)
132{
133 char c = *id++;
134 if (!isalpha(c) && (c != '_') && (c != ':'))
135 return 0;
136 while ((c = *id++)) {
137 if (!isalnum(c) && (c != '_') && (c != ':') &&
138 (c != '-') && (c != '.'))
139 return 0;
140 }
141 return 1;
142}
143
144/* return true if *s points to &[A-Za-z]*; (e.g. &Ccedil; )
145 * or &#[0-9]*; (e.g. &#38; )
146 * or &#x[0-9a-fA-F]*; (e.g. &#x6C34; )
147 */
148static int xml_isentity(char *s)
149{
150 s++; /* already known to be '&' */
151 if (*s == '#') {
152 s++;
153 if (*s == 'x' || *s == 'X') {
154 s++;
155 while ((*s >= '0' && *s <= '9')
156 || (*s >= 'a' && *s <= 'f')
157 || (*s >= 'A' && *s <= 'F'))
158 s++;
159 } else {
160 while (*s >= '0' && *s <= '9')
161 s++;
162 }
163 } else {
164 while ((*s >= 'a' && *s <= 'z')
165 || (*s >= 'A' && *s <= 'Z'))
166 s++;
167 }
168 if (*s == ';')
169 return 1;
170 return 0;
171}
172
173static char *_xml_string(char *s, int notURL)
174{
175 static char *buf = NULL;
176 static int bufsize = 0;
177 char *p, *sub, *prev = NULL;
178 int len, pos = 0;
179
180 if (!buf) {
181 bufsize = 64;
182 buf = N_NEW(bufsize, char);
183 }
184
185 p = buf;
186 while (s && *s) {
187 if (pos > (bufsize - 8)) {
188 bufsize *= 2;
189 buf = realloc(buf, bufsize);
190 p = buf + pos;
191 }
192 /* escape '&' only if not part of a legal entity sequence */
193 if (*s == '&' && !(xml_isentity(s))) {
194 sub = "&amp;";
195 len = 5;
196 }
197 /* '<' '>' are safe to substitute even if string is already UTF-8 coded
198 * since UTF-8 strings won't contain '<' or '>' */
199 else if (*s == '<') {
200 sub = "&lt;";
201 len = 4;
202 }
203 else if (*s == '>') {
204 sub = "&gt;";
205 len = 4;
206 }
207 else if ((*s == '-') && notURL) { /* can't be used in xml comment strings */
208 sub = "&#45;";
209 len = 5;
210 }
211 else if (*s == ' ' && prev && *prev == ' ' && notURL) {
212 /* substitute 2nd and subsequent spaces with required_spaces */
213 sub = "&#160;"; /* inkscape doesn't recognise &nbsp; */
214 len = 6;
215 }
216 else if (*s == '"') {
217 sub = "&quot;";
218 len = 6;
219 }
220 else if (*s == '\'') {
221 sub = "&#39;";
222 len = 5;
223 }
224 else {
225 sub = s;
226 len = 1;
227 }
228 while (len--) {
229 *p++ = *sub++;
230 pos++;
231 }
232 prev = s;
233 s++;
234 }
235 *p = '\0';
236 return buf;
237}
238
239static char *xml_string(char *s)
240{
241 return _xml_string(s,1);
242}
243
244static char *xml_url_string(char *s)
245{
246 return _xml_string(s,0);
247}
248
249static int isGxlGrammar(char *name)
250{
251 return (strncmp(name, GXL_ATTR, (sizeof(GXL_ATTR) - 1)) == 0);
252}
253
254static int isLocatorType(char *name)
255{
256 return (strncmp(name, GXL_LOC, GXL_LOC_LEN) == 0);
257}
258
259static void *idexists(Dt_t * ids, char *id)
260{
261 return dtmatch(ids, id);
262}
263
264/* addid:
265 * assume id is not in ids.
266 */
267static char *addid(Dt_t * ids, char *id)
268{
269 idv_t *idp = NEW(idv_t);
270
271 idp->name = strdup(id);
272 dtinsert(ids, idp);
273 return idp->name;
274}
275
276static char *createGraphId(Dt_t * ids)
277{
278 static int graphIdCounter = 0;
279 char buf[SMALLBUF];
280
281 do {
282 sprintf(buf, "G_%d", graphIdCounter++);
283 } while (idexists(ids, buf));
284 return addid(ids, buf);
285}
286
287static char *createNodeId(Dt_t * ids)
288{
289 static int nodeIdCounter = 0;
290 char buf[SMALLBUF];
291
292 do {
293 sprintf(buf, "N_%d", nodeIdCounter++);
294 } while (idexists(ids, buf));
295 return addid(ids, buf);
296}
297
298static char *mapLookup(Dt_t * nm, char *name)
299{
300 namev_t *objp = dtmatch(nm, name);
301 if (objp)
302 return objp->unique_name;
303 else
304 return 0;
305}
306
307static char *nodeID(gxlstate_t * stp, Agnode_t * n)
308{
309 char *name, *uniqueName;
310
311 name = agnameof(n);
312 uniqueName = mapLookup(stp->nodeMap, name);
313 assert(uniqueName);
314 return uniqueName;
315}
316
317#define EXTRA 32 /* space for ':' followed by a number */
318#define EDGEOP "--" /* cannot use '>'; illegal in ID in GXL */
319
320static char *createEdgeId(gxlstate_t * stp, Agedge_t * e)
321{
322 int edgeIdCounter = 1;
323 char buf[BUFSIZ];
324 char *hname = nodeID(stp, AGHEAD(e));
325 char *tname = nodeID(stp, AGTAIL(e));
326 int baselen = strlen(hname) + strlen(tname) + sizeof(EDGEOP);
327 int len = baselen + EXTRA;
328 char *bp;
329 char *endp; /* where to append ':' and number */
330 char *rv;
331
332 if (len <= BUFSIZ)
333 bp = buf;
334 else
335 bp = N_NEW(len, char);
336 endp = bp + (baselen - 1);
337
338 sprintf(bp, "%s%s%s", tname, EDGEOP, hname);
339 while (idexists(stp->idList, bp)) {
340 sprintf(endp, ":%d", edgeIdCounter++);
341 }
342
343 rv = addid(stp->idList, bp);
344 if (bp != buf)
345 free(bp);
346 return rv;
347}
348
349static void addToMap(Dt_t * map, char *name, char *uniqueName)
350{
351 namev_t obj;
352 namev_t *objp;
353
354 obj.name = name;
355 objp = dtinsert(map, &obj);
356 assert(objp->unique_name == 0);
357 objp->unique_name = uniqueName;
358}
359
360static void graphAttrs(FILE * gxlFile, Agraph_t * g)
361{
362 char *val;
363
364 val = agget(g, GXL_ROLE);
365 if (!EMPTY(val)) {
366 fprintf(gxlFile, " role=\"%s\"", xml_string(val));
367 }
368 val = agget(g, GXL_HYPER);
369 if (!EMPTY(val)) {
370 fprintf(gxlFile, " hypergraph=\"%s\"", xml_string(val));
371 }
372}
373
374static void edgeAttrs(FILE * gxlFile, Agedge_t * e)
375{
376 char *val;
377
378 val = agget(e, GXL_ID);
379 if (!EMPTY(val)) {
380 fprintf(gxlFile, " id=\"%s\"", xml_string(val));
381 }
382 val = agget(e, GXL_FROM);
383 if (!EMPTY(val)) {
384 fprintf(gxlFile, " fromorder=\"%s\"", xml_string(val));
385 }
386 val = agget(e, GXL_TO);
387 if (!EMPTY(val)) {
388 fprintf(gxlFile, " toorder=\"%s\"", xml_string(val));
389 }
390}
391
392
393static void printHref(FILE * gxlFile, void *n)
394{
395 char *val;
396
397 val = agget(n, GXL_TYPE);
398 if (!EMPTY(val)) {
399 tabover(gxlFile);
400 fprintf(gxlFile, "\t<type xlink:href=\"%s\">\n", xml_url_string(val));
401 tabover(gxlFile);
402 fprintf(gxlFile, "\t</type>\n");
403 }
404}
405
406
407static void
408writeDict(Agraph_t * g, FILE * gxlFile, char *name, Dict_t * dict,
409 int isGraph)
410{
411 Dict_t *view;
412 Agsym_t *sym, *psym;
413
414 view = dtview(dict, NIL(Dict_t *));
415 for (sym = (Agsym_t *) dtfirst(dict); sym;
416 sym = (Agsym_t *) dtnext(dict, sym)) {
417 if (!isGxlGrammar(sym->name)) {
418 if (EMPTY(sym->defval)) { /* try to skip empty str (default) */
419 if (view == NIL(Dict_t *))
420 continue; /* no parent */
421 psym = (Agsym_t *) dtsearch(view, sym);
422 /* assert(psym); */
423 if (EMPTY(psym->defval))
424 continue; /* also empty in parent */
425 }
426
427 if (isLocatorType(sym->defval)) {
428 char *locatorVal;
429 locatorVal = sym->defval;
430 locatorVal += 13;
431
432 tabover(gxlFile);
433 fprintf(gxlFile, "\t<attr name=\"%s\">\n", xml_string(sym->name));
434 tabover(gxlFile);
435 fprintf(gxlFile, "\t\t<locator xlink:href=\"%s\"/>\n",
436 xml_url_string(locatorVal));
437 tabover(gxlFile);
438 fprintf(gxlFile, "\t</attr>\n");
439 } else {
440 tabover(gxlFile);
441 if (isGraph) {
442 fprintf(gxlFile, "\t<attr name=\"%s\" ", xml_string(sym->name));
443 fprintf(gxlFile, "kind=\"%s\">\n", xml_string(name));
444 }
445 else {
446 fprintf(gxlFile, "\t<attr name=\"%s:", xml_string(name));
447 fprintf(gxlFile, "%s\" kind=\"", xml_string(sym->name));
448 fprintf(gxlFile, "%s\">\n", xml_string(name));
449 }
450 tabover(gxlFile);
451 fprintf(gxlFile, "\t\t<string>%s</string>\n", xml_string(sym->defval));
452 tabover(gxlFile);
453 fprintf(gxlFile, "\t</attr>\n");
454 }
455 } else {
456 /* gxl attr; check for special cases like composites */
457 if (strncmp(sym->name, GXL_COMP, GXL_COMP_LEN) == 0) {
458 if (EMPTY(sym->defval)) {
459 if (view == NIL(Dict_t *))
460 continue;
461 psym = (Agsym_t *) dtsearch(view, sym);
462 if (EMPTY(psym->defval))
463 continue;
464 }
465
466 tabover(gxlFile);
467 fprintf(gxlFile, "\t<attr name=\"%s\" ", xml_string(((sym->name) + GXL_COMP_LEN)));
468 fprintf(gxlFile, "kind=\"%s\">\n", xml_string(name));
469 tabover(gxlFile);
470 fprintf(gxlFile, "\t\t%s\n", xml_string(sym->defval));
471 tabover(gxlFile);
472 fprintf(gxlFile, "\t</attr>\n");
473 }
474 }
475 }
476 dtview(dict, view); /* restore previous view */
477}
478
479static void writeDicts(Agraph_t * g, FILE * gxlFile)
480{
481 Agdatadict_t *def;
482 if ((def = (Agdatadict_t *) agdatadict(g, FALSE))) {
483 writeDict(g, gxlFile, "graph", def->dict.g, 1);
484 writeDict(g, gxlFile, "node", def->dict.n, 0);
485 writeDict(g, gxlFile, "edge", def->dict.e, 0);
486 }
487}
488
489static void
490writeHdr(gxlstate_t * stp, Agraph_t * g, FILE * gxlFile, int top)
491{
492 char *name;
493 char *kind;
494 char *uniqueName;
495 char buf[BUFSIZ];
496 char *bp;
497 char *dynbuf = 0;
498 int len;
499
500 Level++;
501 stp->attrsNotWritten = AGATTRWF(g);
502
503 name = agnameof(g);
504 if (g->desc.directed)
505 kind = "directed";
506 else
507 kind = "undirected";
508 if (!top && agparent(g)) {
509 /* this must be anonymous graph */
510
511 len = strlen(name) + sizeof("N_");
512 if (len <= BUFSIZ)
513 bp = buf;
514 else {
515 bp = dynbuf = N_NEW(len, char);
516 }
517 sprintf(bp, "N_%s", name);
518 if (idexists(stp->idList, bp) || !legalGXLName(bp)) {
519 bp = createNodeId(stp->idList);
520 } else {
521 bp = addid(stp->idList, bp);
522 }
523 addToMap(stp->synNodeMap, name, bp);
524
525 tabover(gxlFile);
526 fprintf(gxlFile, "<node id=\"%s\">\n", bp);
527 if (dynbuf)
528 free(dynbuf);
529 Level++;
530 } else {
531 Tailport = agattr(g, AGEDGE, "tailport", NIL(char *));
532 Headport = agattr(g, AGEDGE, "headport", NIL(char *));
533 }
534
535
536 uniqueName = mapLookup(stp->graphMap, name);
537 tabover(gxlFile);
538 fprintf(gxlFile, "<graph id=\"%s\" edgeids=\"true\" edgemode=\"%s\"",
539 uniqueName, kind);
540 graphAttrs(gxlFile, g);
541 fprintf(gxlFile, ">\n");
542
543 if (uniqueName && (strcmp(name, uniqueName) != 0)) {
544 tabover(gxlFile);
545 fprintf(gxlFile, "\t<attr name=\"name\">\n");
546 tabover(gxlFile);
547 fprintf(gxlFile, "\t\t<string>%s</string>\n", xml_string(name));
548 tabover(gxlFile);
549 fprintf(gxlFile, "\t</attr>\n");
550 }
551
552 if (agisstrict(g)) {
553 tabover(gxlFile);
554 fprintf(gxlFile, "\t<attr name=\"strict\">\n");
555 tabover(gxlFile);
556 fprintf(gxlFile, "\t\t<string>true</string>\n");
557 tabover(gxlFile);
558 fprintf(gxlFile, "\t</attr>\n");
559 }
560
561 writeDicts(g, gxlFile);
562 printHref(gxlFile, g);
563 AGATTRWF(g) = !(AGATTRWF(g));
564}
565
566static void writeTrl(Agraph_t * g, FILE * gxlFile, int top)
567{
568 tabover(gxlFile);
569 fprintf(gxlFile, "</graph>\n");
570 Level--;
571 if (!(top) && agparent(g)) {
572 tabover(gxlFile);
573 fprintf(gxlFile, "</node>\n");
574 Level--;
575 }
576}
577
578
579static void writeSubgs(gxlstate_t * stp, Agraph_t * g, FILE * gxlFile)
580{
581 Agraph_t *subg;
582
583 for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
584 writeHdr(stp, subg, gxlFile, FALSE);
585 writeBody(stp, subg, gxlFile);
586 writeTrl(subg, gxlFile, FALSE);
587 }
588}
589
590static int writeEdgeName(Agedge_t * e, FILE * gxlFile, int terminate)
591{
592 int rv;
593 char *p;
594
595 p = agnameof(e);
596 if (!(EMPTY(p))) {
597 tabover(gxlFile);
598 fprintf(gxlFile, "\t<attr name=\"key\">\n");
599 tabover(gxlFile);
600 fprintf(gxlFile, "\t\t<string>%s</string>\n", xml_string(p));
601 tabover(gxlFile);
602 fprintf(gxlFile, "\t</attr>\n");
603 rv = TRUE;
604 } else
605 rv = FALSE;
606 return rv;
607}
608
609
610static void
611writeNondefaultAttr(void *obj, FILE * gxlFile, Dict_t * defdict)
612{
613 Agattr_t *data;
614 Agsym_t *sym;
615 int cnt = 0;
616
617 if ((AGTYPE(obj) == AGINEDGE) || (AGTYPE(obj) == AGOUTEDGE)) {
618 if (writeEdgeName(obj, gxlFile, FALSE))
619 cnt++;
620 }
621 data = (Agattr_t *) agattrrec(obj);
622 if (data) {
623 for (sym = (Agsym_t *) dtfirst(defdict); sym;
624 sym = (Agsym_t *) dtnext(defdict, sym)) {
625 if (!isGxlGrammar(sym->name)) {
626 if ((AGTYPE(obj) == AGINEDGE)
627 || (AGTYPE(obj) == AGOUTEDGE)) {
628 if (Tailport && (sym->id == Tailport->id))
629 continue;
630 if (Headport && (sym->id == Headport->id))
631 continue;
632 }
633 if (data->str[sym->id] != sym->defval) {
634
635 if (strcmp(data->str[sym->id], "") == 0)
636 continue;
637
638 if (isLocatorType(data->str[sym->id])) {
639 char *locatorVal;
640 locatorVal = data->str[sym->id];
641 locatorVal += 13;
642
643 tabover(gxlFile);
644 fprintf(gxlFile, "\t<attr name=\"%s\">\n",
645 xml_string(sym->name));
646 tabover(gxlFile);
647 fprintf(gxlFile,
648 "\t\t<locator xlink:href=\"%s\"/>\n",
649 xml_url_string(locatorVal));
650 tabover(gxlFile);
651 fprintf(gxlFile, "\t</attr>\n");
652 } else {
653 tabover(gxlFile);
654 fprintf(gxlFile, "\t<attr name=\"%s\">\n",
655 xml_string(sym->name));
656 tabover(gxlFile);
657 fprintf(gxlFile, "\t\t<string>%s</string>\n", xml_string(data->str[sym->id]));
658 tabover(gxlFile);
659 fprintf(gxlFile, "\t</attr>\n");
660 }
661 }
662 } else {
663 /* gxl attr; check for special cases like composites */
664 if (strncmp(sym->name, GXL_COMP, GXL_COMP_LEN) == 0) {
665 if (data->str[sym->id] != sym->defval) {
666
667 tabover(gxlFile);
668 fprintf(gxlFile, "\t<attr name=\"%s\">\n",
669 xml_string(((sym->name) + GXL_COMP_LEN)));
670 tabover(gxlFile);
671 fprintf(gxlFile, "\t\t%s\n", xml_string(data->str[sym->id]));
672 tabover(gxlFile);
673 fprintf(gxlFile, "\t</attr>\n");
674 }
675 }
676 }
677 }
678 }
679 AGATTRWF((Agobj_t *) obj) = !(AGATTRWF((Agobj_t *) obj));
680}
681
682/* nodeID:
683 * Return id associated with the given node.
684 */
685static int attrs_written(gxlstate_t * stp, void *obj)
686{
687 return !(AGATTRWF((Agobj_t *) obj) == stp->attrsNotWritten);
688}
689
690static void
691writeNode(gxlstate_t * stp, Agnode_t * n, FILE * gxlFile, Dict_t * d)
692{
693 char *name, *uniqueName;
694
695 name = agnameof(n);
696 uniqueName = nodeID(stp, n);
697 Level++;
698 tabover(gxlFile);
699 fprintf(gxlFile, "<node id=\"%s\">\n", uniqueName);
700
701 printHref(gxlFile, n);
702
703 if (strcmp(name, uniqueName)) {
704 tabover(gxlFile);
705 fprintf(gxlFile, "\t<attr name=\"name\">\n");
706 tabover(gxlFile);
707 fprintf(gxlFile, "\t\t<string>%s</string>\n", xml_string(name));
708 tabover(gxlFile);
709 fprintf(gxlFile, "\t</attr>\n");
710 }
711
712 if (!attrs_written(stp, n))
713 writeNondefaultAttr(n, gxlFile, d);
714 tabover(gxlFile);
715 fprintf(gxlFile, "</node>\n");
716 Level--;
717}
718
719static void writePort(Agedge_t * e, FILE * gxlFile, char *name)
720{
721 char *val;
722
723 val = agget(e, name);
724 if (val && val[0]) {
725 tabover(gxlFile);
726 fprintf(gxlFile, "\t<attr name=\"%s\">\n", xml_string(name));
727 tabover(gxlFile);
728 fprintf(gxlFile, "\t\t<string>%s</string>\n", xml_string(val));
729 tabover(gxlFile);
730 fprintf(gxlFile, "\t</attr>\n");
731 }
732}
733
734static int writeEdgeTest(Agraph_t * g, Agedge_t * e)
735{
736 Agraph_t *subg;
737
738 /* can use agedge() because we subverted the dict compar_f */
739 for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
740 if (agsubedge(subg, e, FALSE))
741 return FALSE;
742 }
743 return TRUE;
744}
745
746static void
747writeEdge(gxlstate_t * stp, Agedge_t * e, FILE * gxlFile, Dict_t * d)
748{
749 Agnode_t *t, *h;
750 char *bp;
751 char *edge_id;
752
753 t = AGTAIL(e);
754 h = AGHEAD(e);
755
756 Level++;
757 tabover(gxlFile);
758 fprintf(gxlFile, "<edge from=\"%s\" ", nodeID(stp, t));
759 fprintf(gxlFile, "to=\"%s\"", nodeID(stp, h));
760 edgeAttrs(gxlFile, e);
761
762 if (stp->directed) {
763 fprintf(gxlFile, " isdirected=\"true\"");
764 } else {
765 fprintf(gxlFile, " isdirected=\"false\"");
766 }
767
768 edge_id = agget(e, GXL_ID);
769 if (!EMPTY(edge_id)) {
770 fprintf(gxlFile, ">\n");
771 } else {
772 bp = createEdgeId(stp, e);
773 fprintf(gxlFile, " id=\"%s\">\n", bp);
774 }
775
776 printHref(gxlFile, e);
777
778 writePort(e, gxlFile, "tailport");
779 writePort(e, gxlFile, "headport");
780 if (!(attrs_written(stp, e)))
781 writeNondefaultAttr(e, gxlFile, d);
782 else
783 writeEdgeName(e, gxlFile, TRUE);
784 tabover(gxlFile);
785 fprintf(gxlFile, "</edge>\n");
786 Level--;
787}
788
789
790#define writeval(n) (((Agnodeinfo_t*)((n)->base.data))->written)
791
792static void writeBody(gxlstate_t * stp, Agraph_t * g, FILE * gxlFile)
793{
794 Agnode_t *n;
795 Agnode_t *realn;
796 Agedge_t *e;
797 Agdatadict_t *dd;
798
799 writeSubgs(stp, g, gxlFile);
800 dd = (Agdatadict_t *) agdatadict(g, FALSE);
801 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
802 realn = agidnode(stp->root, AGID(n), 0);
803 if (!writeval(realn)) {
804 writeval(realn) = 1;
805 writeNode(stp, n, gxlFile, dd->dict.n);
806 }
807
808 for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
809 if (writeEdgeTest(g, e))
810 writeEdge(stp, e, gxlFile, dd->dict.e);
811 }
812 }
813}
814
815static void iterateHdr(gxlstate_t * stp, Agraph_t * g)
816{
817 char *gxlId;
818 char *name;
819
820 name = agnameof(g);
821 gxlId = agget(g, GXL_ID);
822 if (EMPTY(gxlId))
823 gxlId = name;
824
825 if (idexists(stp->idList, gxlId) || !legalGXLName(gxlId))
826 gxlId = createGraphId(stp->idList);
827 else
828 gxlId = addid(stp->idList, gxlId);
829 addToMap(stp->graphMap, name, gxlId);
830}
831
832static void iterate_subgs(gxlstate_t * stp, Agraph_t * g)
833{
834 Agraph_t *subg;
835
836 for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
837 iterateHdr(stp, subg);
838 iterateBody(stp, subg);
839 }
840}
841
842
843static void iterateBody(gxlstate_t * stp, Agraph_t * g)
844{
845 Agnode_t *n;
846 Agedge_t *e;
847
848 iterate_subgs(stp, g);
849 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
850 char *gxlId;
851 char *nodename = agnameof(n);
852
853 if (!mapLookup(stp->nodeMap, nodename)) {
854 gxlId = agget(n, GXL_ID);
855 if (EMPTY(gxlId))
856 gxlId = nodename;
857 if (idexists(stp->idList, gxlId) || !legalGXLName(gxlId))
858 gxlId = createNodeId(stp->idList);
859 else
860 gxlId = addid(stp->idList, gxlId);
861 addToMap(stp->nodeMap, nodename, gxlId);
862 }
863
864 for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
865 if (writeEdgeTest(g, e)) {
866 char *edge_id = agget(e, GXL_ID);
867 if (!EMPTY(edge_id))
868 addid(stp->idList, edge_id);
869 }
870 }
871 }
872}
873
874static gxlstate_t *initState(Agraph_t * g)
875{
876 gxlstate_t *stp = NEW(gxlstate_t);
877 stp->nodeMap = dtopen(&nameDisc, Dtoset);
878 stp->graphMap = dtopen(&nameDisc, Dtoset);
879 stp->synNodeMap = dtopen(&nameDisc, Dtoset);
880 stp->idList = dtopen(&idDisc, Dtoset);
881 stp->attrsNotWritten = 0;
882 stp->root = g;
883 stp->directed = agisdirected(g);
884 return stp;
885}
886
887static void freeState(gxlstate_t * stp)
888{
889 dtclose(stp->nodeMap);
890 dtclose(stp->graphMap);
891 dtclose(stp->synNodeMap);
892 dtclose(stp->idList);
893 free(stp);
894}
895
896void gv_to_gxl(Agraph_t * g, FILE * gxlFile)
897{
898 gxlstate_t *stp = initState(g);
899 aginit(g, AGNODE, "node", sizeof(Agnodeinfo_t), TRUE);
900
901 iterateHdr(stp, g);
902 iterateBody(stp, g);
903
904 Level = 0;
905
906 fprintf(gxlFile, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n");
907 fprintf(gxlFile, "<gxl>\n");
908
909 writeHdr(stp, g, gxlFile, TRUE);
910 writeBody(stp, g, gxlFile);
911 writeTrl(g, gxlFile, TRUE);
912
913 fprintf(gxlFile, "</gxl>\n");
914
915 freeState(stp);
916}
917