]> granicus.if.org Git - graphviz/commitdiff
Add gml to gv converter
authorerg <devnull@localhost>
Wed, 8 Jul 2009 21:00:58 +0000 (21:00 +0000)
committererg <devnull@localhost>
Wed, 8 Jul 2009 21:00:58 +0000 (21:00 +0000)
cmd/tools/Makefile.am
cmd/tools/gml2gv.1 [new file with mode: 0644]
cmd/tools/gml2gv.c [new file with mode: 0644]
cmd/tools/gml2gv.h [new file with mode: 0644]
cmd/tools/gmlparse.y [new file with mode: 0644]
cmd/tools/gmlscan.l [new file with mode: 0644]

index 8d42f8b5c54a6f6b5e7ff91e6a801ab5b0edd50b..368b52d6fc864cac44d7983076303e8abaf6def0 100644 (file)
@@ -18,13 +18,13 @@ pdfdir = $(pkgdatadir)/doc/pdf
 
 noinst_HEADERS = colortbl.h convert.h mmio.h matrix_market.h graph_generator.h
 bin_PROGRAMS = gc gvcolor gxl2gv acyclic nop ccomps sccmap tred \
-       unflatten gvpack dijkstra bcomps mm2gv gvgen
+       unflatten gvpack dijkstra bcomps mm2gv gvgen gml2gv
 man_MANS = gc.1 gvcolor.1 gxl2gv.1 acyclic.1 nop.1 ccomps.1 sccmap.1 \
-       tred.1 unflatten.1 gvpack.1 dijkstra.1 bcomps.1 mm2gv.1 gvgen.1
+       tred.1 unflatten.1 gvpack.1 dijkstra.1 bcomps.1 mm2gv.1 gvgen.1 gml2gv.1
 pdf_DATA = gc.1.pdf gvcolor.1.pdf gxl2gv.1.pdf acyclic.1.pdf \
           nop.1.pdf ccomps.1.pdf sccmap.1.pdf tred.1.pdf \
           unflatten.1.pdf gvpack.1.pdf dijkstra.1.pdf \
-          bcomps.1.pdf mm2gv.1.pdf gvgen.1.pdf
+          bcomps.1.pdf mm2gv.1.pdf gvgen.1.pdf gml2gv.1.pdf
 
 install-data-hook:
        (cd $(DESTDIR)$(man1dir); rm -f gv2gxl.1; $(LN_S) gxl2gv.1 gv2gxl.1;)
@@ -153,6 +153,30 @@ mm2gv_LDADD = \
 mm2gv.1.pdf: mm2gv.1
        - @GROFF@ -Tps -man mm2gv.1 | @PS2PDF@ - - >mm2gv.1.pdf
 
+gml2gv_SOURCES = gml2gv.c gmlparse.c gmlscan.c
+
+gmlparse.c: y.tab.c
+       @SED@ "s/yy/gml/g" < y.tab.c > gmlparse.c
+
+gmlparse.h: y.tab.h
+       @SED@ "s/yy/gml/g" < y.tab.h > gmlparse.h
+
+y.tab.c y.tab.h : $(top_srcdir)/cmd/tools/gmlparse.y
+       @YACC@ -dv $(top_srcdir)/cmd/tools/gmlparse.y
+gmlscan.o gmlscan.lo : gmlscan.c gmlparse.h
+
+gmlscan.c: $(top_srcdir)/cmd/tools/gmlscan.l
+       @LEX@ -i $(top_srcdir)/cmd/tools/gmlscan.l
+       @SED@ "s/yy/gml/g" < @LEX_OUTPUT_ROOT@.c > gmlscan.c
+       rm @LEX_OUTPUT_ROOT@.c
+
+gml2gv_LDADD = \
+       $(top_builddir)/lib/cgraph/libcgraph.la @MATH_LIBS@
+
+gml2gv.1.pdf: gml2gv.1
+       - @GROFF@ -Tps -man gml2gv.1 | @PS2PDF@ - - >gml2gv.1.pdf
+
 dijkstra_SOURCES = dijkstra.c
 
 dijkstra_LDADD = \
@@ -179,4 +203,4 @@ EXTRA_DIST = $(man_MANS) $(pdf_DATA) Makefile.old bcomps.vcproj \
 
 CLEANFILES = stamp.h
 
-DISTCLEANFILES = $(pdf_DATA)
+DISTCLEANFILES = $(pdf_DATA) gmlparse.[ch] gmlscan.c y.output y.tab.[ch]
diff --git a/cmd/tools/gml2gv.1 b/cmd/tools/gml2gv.1
new file mode 100644 (file)
index 0000000..14fa6a6
--- /dev/null
@@ -0,0 +1,52 @@
+.TH GML2GV 1 "9 July 2009"
+.SH NAME
+gml2gv \- GML-DOT converter
+.SH SYNOPSIS
+.B gml2gv
+[
+.B \-?
+]
+[
+.BI -o outfile
+]
+[ 
+.I files
+]
+.br
+.SH DESCRIPTION
+.B gml2gv
+converts a graph specified in the GML format to a graph in the GV (formerly DOT) format. 
+.SH OPTIONS
+The following options are supported:
+.TP
+.B \-?
+Prints usage information and exits.
+.TP
+.BI \-o "outfile"
+Prints output to the file \fIoutfile\fP. If not given, \fBgml2gv\fP
+uses stdout.
+.TP
+.SH OPERANDS
+The following operand is supported:
+.TP 8
+.I files
+Names of files containing 1 or more graphs in GML.
+If no
+.I files
+operand is specified,
+the standard input will be used.
+.SH RETURN CODES
+Return \fB0\fP
+if there were no problems during conversion;
+and non-zero if any error occurred.
+.SH "LIMITATIONS"
+As both the graph and graphics models of GV and GML differ significantly, the
+conversion is at best approximate. In particular, it is not clear how multiedges
+are differentiated in GML, so multiedges are created in GV with no user-available
+key. Also, no attribute information is lost, in that
+any GML attributes that aren't converted to GV equivalents are retained as
+attributes in the output graph.
+.SH AUTHORS
+Emden R. Gansner <erg@research.att.com>
+.SH "SEE ALSO"
+dot(1), libcgraph(3)
diff --git a/cmd/tools/gml2gv.c b/cmd/tools/gml2gv.c
new file mode 100644 (file)
index 0000000..3eb1b6e
--- /dev/null
@@ -0,0 +1,155 @@
+/* $Id$Revision: */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+*      This software is part of the graphviz package      *
+*                http://www.graphviz.org/                 *
+*                                                         *
+*            Copyright (c) 1994-2004 AT&T Corp.           *
+*                and is licensed under the                *
+*            Common Public License, Version 1.0           *
+*                      by AT&T Corp.                      *
+*                                                         *
+*        Information and Software Systems Research        *
+*              AT&T Research, Florham Park NJ             *
+**********************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gml2gv.h"
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+#include "compat_getopt.h"
+#endif
+
+static FILE *outFile;
+static char *CmdName;
+static char **Files;
+
+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 FILE *openFile(char *name, char *mode)
+{
+    FILE *fp;
+    char *modestr;
+
+    fp = fopen(name, mode);
+    if (!fp) {
+        if (*mode == 'r')
+            modestr = "reading";
+        else
+            modestr = "writing";
+        fprintf(stderr, "%s: could not open file %s for %s\n",
+                CmdName, name, modestr);
+        perror(name);
+        exit(1);
+    }
+    return fp;
+}
+
+
+static char *useString = "Usage: nop [-p?] <files>\n\
+  -o<file>  : output to <file> (stdout)\n\
+  -? - print usage\n\
+If no files are specified, stdin is used\n";
+
+static void usage(int v)
+{
+    printf(useString);
+    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]);
+    while ((c = getopt(argc, argv, ":gdo:?")) != -1) {
+       switch (c) {
+       case 'o':
+           outFile = openFile(optarg, "w");
+           break;
+       case '?':
+           if (optopt == '?')
+               usage(0);
+           else {
+               fprintf(stderr, "%s: option -%c unrecognized\n", CmdName,
+                       optopt);
+               exit(1);
+           }
+       }
+    }
+
+    argv += optind;
+    argc -= optind;
+
+    if (argc)
+       Files = argv;
+    if (!outFile)
+       outFile = stdout;
+}
+
+int main(int argc, char **argv)
+{
+    Agraph_t *G;
+    Agraph_t *prev = 0;
+    FILE *inFile;
+    int cnt, rv;
+
+    rv = 0;
+    initargs(argc, argv);
+    while ((inFile = getFile())) {
+       cnt = 0;
+       while ((G = gml_to_gv(inFile, cnt, &rv))) {
+           cnt++;
+           if (prev)
+               agclose(prev);
+           prev = G;
+           agwrite(G, outFile);
+           fflush(outFile);
+       }
+    }
+    exit(rv);
+}
diff --git a/cmd/tools/gml2gv.h b/cmd/tools/gml2gv.h
new file mode 100644 (file)
index 0000000..b7e5cce
--- /dev/null
@@ -0,0 +1,46 @@
+/* $Id$Revision: */
+/* vim:set shiftwidth=4 ts=8: */
+
+#include <stdio.h>
+#include <cgraph.h>
+
+typedef struct {
+    Dtlink_t link;
+    unsigned short kind;
+    unsigned short sort;
+    char* name;
+    union {
+       char* value;
+       Dt_t* lp;
+    }u;
+} gmlattr;
+
+typedef struct {
+    Dtlink_t link;
+    char* id;
+    Dt_t* attrlist;  
+} gmlnode;
+
+typedef struct {
+    Dtlink_t link;
+    char* source;
+    char* target;
+    Dt_t* attrlist;  
+} gmledge;
+
+typedef struct gmlgraph {
+    Dtlink_t link;
+    struct gmlgraph* parent;
+    int directed;
+    Dt_t* attrlist;  
+    Dt_t* nodelist;  
+    Dt_t* edgelist;  
+    Dt_t* graphlist;  
+} gmlgraph;
+
+extern int gmllex(void);
+extern void gmllexeof(void);
+extern void gmlerror(char *);
+extern int gmlerrors(void);
+extern void initgmlscan (FILE*);
+extern Agraph_t* gml_to_gv (FILE*, int, int*);
diff --git a/cmd/tools/gmlparse.y b/cmd/tools/gmlparse.y
new file mode 100644 (file)
index 0000000..ba62539
--- /dev/null
@@ -0,0 +1,808 @@
+/* $Id$Revision: */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+*      This software is part of the graphviz package      *
+*                http://www.graphviz.org/                 *
+*                                                         *
+*            Copyright (c) 1994-2004 AT&T Corp.           *
+*                and is licensed under the                *
+*            Common Public License, Version 1.0           *
+*                      by AT&T Corp.                      *
+*                                                         *
+*        Information and Software Systems Research        *
+*              AT&T Research, Florham Park NJ             *
+**********************************************************/
+
+%{
+#include <stdlib.h>
+#include <stddef.h>
+#include <arith.h>
+#include <gml2gv.h>
+#include <agxbuf.h>
+#include <assert.h>
+#define NEW(t)       (t*)malloc(sizeof(t))
+#define N_NEW(n,t)   (t*)malloc((n)*sizeof(t))
+#define RALLOC(size,ptr,type) ((type*)realloc(ptr,(size)*sizeof(type)))
+
+typedef unsigned short ushort;
+
+static gmlgraph* G;
+static gmlnode* N;
+static gmledge* E;
+static Dt_t* L;
+static Dt_t** liststk;
+static int liststk_sz, liststk_cnt;
+static void
+free_node (Dt_t*d, gmlnode* p, Dtdisc_t* ds)
+{
+    if (!p) return;
+    dtclose (p->attrlist);
+    free (p);
+}
+
+static void
+free_edge (Dt_t*d, gmledge* p, Dtdisc_t* ds)
+{
+    if (!p) return;
+    dtclose (p->attrlist);
+    free (p);
+}
+
+static void
+free_attr (Dt_t*d, gmlattr* p, Dtdisc_t* ds)
+{
+    if (!p) return;
+    if (p->kind == LIST)
+       dtclose (p->u.lp);
+    else
+       free (p->u.value);
+    free (p->name);
+    free (p);
+}
+
+static void
+free_graph (Dt_t*d, gmlgraph* p, Dtdisc_t* ds)
+{
+    if (!p) return;
+    if (p->nodelist)
+       dtclose (p->nodelist);
+    if (p->edgelist)
+       dtclose (p->edgelist);
+    if (p->attrlist)
+       dtclose (p->attrlist);
+    if (p->graphlist)
+       dtclose (p->graphlist);
+    free (p);
+}
+
+static Dtdisc_t nodeDisc = {
+    offsetof(gmlnode,attrlist),
+    sizeof(Dt_t*),
+    offsetof(gmlnode,link),
+    NIL(Dtmake_f),
+    (Dtfree_f)free_node,
+    NIL(Dtcompar_f),
+    NIL(Dthash_f),
+    NIL(Dtmemory_f),
+    NIL(Dtevent_f)
+};
+
+static Dtdisc_t edgeDisc = {
+    offsetof(gmledge,attrlist),
+    sizeof(Dt_t*),
+    offsetof(gmledge,link),
+    NIL(Dtmake_f),
+    (Dtfree_f)free_edge,
+    NIL(Dtcompar_f),
+    NIL(Dthash_f),
+    NIL(Dtmemory_f),
+    NIL(Dtevent_f)
+};
+
+static Dtdisc_t attrDisc = {
+    offsetof(gmlattr,name),
+    sizeof(char*),
+    offsetof(gmlattr,link),
+    NIL(Dtmake_f),
+    (Dtfree_f)free_attr,
+    NIL(Dtcompar_f),
+    NIL(Dthash_f),
+    NIL(Dtmemory_f),
+    NIL(Dtevent_f)
+};
+
+static Dtdisc_t graphDisc = {
+    offsetof(gmlgraph,nodelist),
+    sizeof(Dt_t*),
+    offsetof(gmlgraph,link),
+    NIL(Dtmake_f),
+    (Dtfree_f)free_graph,
+    NIL(Dtcompar_f),
+    NIL(Dthash_f),
+    NIL(Dtmemory_f),
+    NIL(Dtevent_f)
+};
+
+static void
+initstk ()
+{
+    liststk_sz = 10;
+    liststk_cnt = 0;
+    liststk = N_NEW(liststk_sz, Dt_t*);
+}
+
+static void
+cleanup ()
+{
+    int i;
+
+    if (liststk) {
+       for (i = 0; i < liststk_cnt; i++)
+           dtclose (liststk[i]);
+       free (liststk);
+       liststk = NULL;
+    }
+    if (L) {
+       dtclose (L);
+       L = NULL;
+    }
+    if (N) {
+       free_node (0, N, 0);
+       N = NULL;
+    }
+    if (E) {
+       free_edge (0, E, 0);
+       E = NULL;
+    }
+    if (G) {
+       free_graph (0, G, 0);
+       G = NULL;
+    }
+}
+
+static void
+pushAlist ()
+{
+    Dt_t* lp = dtopen (&attrDisc, Dtqueue);
+
+    if (L) {
+       if (liststk_cnt == liststk_sz) {
+           liststk_sz *= 2;
+           liststk = RALLOC(liststk_sz, liststk, Dt_t*);
+       }
+       liststk[liststk_cnt++] = L;
+    }
+    L = lp;
+}
+
+static Dt_t*
+popAlist ()
+{
+    Dt_t* lp = L;
+
+    if (liststk_cnt)
+       L = liststk[--liststk_cnt];
+    else
+       L = NULL;
+
+    return lp;
+}
+
+static void
+popG ()
+{
+    G = G->parent;
+}
+
+static void
+pushG ()
+{
+    gmlgraph* g = NEW(gmlgraph);
+
+    g->attrlist = dtopen(&attrDisc, Dtqueue);
+    g->nodelist = dtopen(&nodeDisc, Dtqueue);
+    g->edgelist = dtopen(&edgeDisc, Dtqueue);
+    g->graphlist = dtopen(&graphDisc, Dtqueue);
+    g->parent = G;
+    g->directed = -1;
+
+    if (G)
+       dtinsert (G->graphlist, g);
+
+    G = g;
+}
+
+static gmlnode*
+mkNode ()
+{
+    gmlnode* np = NEW(gmlnode);
+    np->attrlist = dtopen (&attrDisc, Dtqueue);
+    np->id = NULL;
+    return np;
+}
+
+static gmledge*
+mkEdge ()
+{
+    gmledge* ep = NEW(gmledge);
+    ep->attrlist = dtopen (&attrDisc, Dtqueue);
+    ep->source = NULL;
+    ep->target = NULL;
+    return ep;
+}
+
+static gmlattr*
+mkAttr (char* name, int sort, int kind, char* str, Dt_t* list)
+{
+    gmlattr* gp = NEW(gmlattr);
+
+    assert (name || sort);
+    gp->sort = sort;
+    gp->kind = kind;
+    gp->name = name;
+    if (str)
+       gp->u.value = str;
+    else {
+       if (dtsize (list) == 0) {
+           dtclose (list);
+           list = 0;
+       }
+       gp->u.lp = list;
+    }
+/* fprintf (stderr, "[%x] %d %d \"%s\" \"%s\" \n", gp, sort, kind, (name?name:""),  (str?str:"")); */
+    return gp;
+}
+
+static int
+setDir (char* d)
+{
+    gmlgraph* g;
+    int dir = atoi (d);
+
+    free (d);
+    if (dir < 0) dir = -1;
+    else if (dir > 0) dir = 1;
+    else dir = 0;
+    G->directed = dir;
+
+    if (dir >= 0) {
+       for (g = G->parent; g; g = g->parent) {
+           if (g->directed < 0)
+               g->directed = dir;
+           else if (g->directed != dir)
+               return 1;
+        }
+    }
+
+    return 0;
+}
+
+%}
+%union  {
+    int i;
+    char *str;
+    gmlnode* np;
+    gmledge* ep;
+    gmlattr* ap;
+    Dt_t*    list;
+}
+
+%token GRAPH NODE EDGE DIRECTED ID SOURCE TARGET
+%token XVAL YVAL WVAL HVAL LABEL GRAPHICS LABELGRAPHICS TYPE FILL
+%token OUTLINE OUTLINESTYLE OUTLINEWIDTH WIDTH
+%token STYLE LINE POINT
+%token TEXT FONTSIZE FONTNAME COLOR
+%token <str> INTEGER REAL STRING NAME
+%token <list> LIST
+
+%type <np> node
+%type <ep> edge
+%type <list> attrlist
+%type <ap> alistitem
+
+%%
+graph : optalist hdr body  {gmllexeof(); if (G->parent) popG(); }
+      | error { cleanup(); YYABORT; }
+      | 
+      ;
+
+hdr   : GRAPH { pushG(); }
+      ;
+
+body  : '[' optglist ']'
+      ;
+
+optglist : glist
+         |  /* empty */ 
+         ;
+
+glist  : glist glistitem
+       | glistitem
+       ;
+
+glistitem : node { dtinsert (G->nodelist, $1); }
+          | edge { dtinsert (G->edgelist, $1); }
+          | hdr body 
+          | DIRECTED INTEGER { 
+               if (setDir($2)) { 
+                   yyerror("mixed directed and undirected graphs"); 
+                   cleanup ();
+                   YYABORT;
+               }
+         }
+          | alistitem { dtinsert (G->attrlist, $1); }
+          ;
+
+node :  NODE { N = mkNode(); } '[' nlist ']' { $$ = N; N = NULL; }
+     ;
+
+nlist : nlist nlistitem
+      | nlistitem
+      ;
+
+nlistitem : ID INTEGER { N->id = $2; }
+          | alistitem { dtinsert (N->attrlist, $1); }
+          ;
+
+edge :  EDGE { E = mkEdge(); } '[' elist ']' { $$ = E; E = NULL; }
+     ;
+
+elist : elist elistitem
+      | elistitem
+      ;
+
+elistitem : SOURCE INTEGER { E->source = $2; }
+          | TARGET INTEGER { E->target = $2; }
+          | alistitem { dtinsert (E->attrlist, $1); }
+          ;
+
+attrlist  : '[' {pushAlist(); } optalist ']' { $$ = popAlist(); }
+          ;
+
+optalist  : alist
+          | /* empty */ 
+          ;
+
+alist  : alist alistitem  { dtinsert (L, $2); }
+       | alistitem  { dtinsert (L, $1); }
+       ;
+
+alistitem : NAME INTEGER { $$ = mkAttr ($1, 0, INTEGER, $2, 0); }
+          | NAME REAL    { $$ = mkAttr ($1, 0, REAL, $2, 0); }
+          | NAME STRING  { $$ = mkAttr ($1, 0, STRING, $2, 0); }
+          | NAME attrlist  { $$ = mkAttr ($1, 0, LIST, 0, $2); }
+          | XVAL REAL  { $$ = mkAttr (0, XVAL, REAL, $2, 0); }
+          | YVAL REAL  { $$ = mkAttr (0, YVAL, REAL, $2, 0); }
+          | WVAL REAL  { $$ = mkAttr (0, WVAL, REAL, $2, 0); }
+          | HVAL REAL  { $$ = mkAttr (0, HVAL, REAL, $2, 0); }
+          | LABEL STRING  { $$ = mkAttr (0, LABEL, STRING, $2, 0); }
+          | GRAPHICS attrlist  { $$ = mkAttr (0, GRAPHICS, LIST, 0, $2); }
+          | LABELGRAPHICS attrlist  { $$ = mkAttr (0, LABELGRAPHICS, LIST, 0, $2); }
+          | TYPE STRING  { $$ = mkAttr (0, TYPE, STRING, $2, 0); }
+          | FILL STRING  { $$ = mkAttr (0, FILL, STRING, $2, 0); }
+          | OUTLINE STRING  { $$ = mkAttr (0, OUTLINE, STRING, $2, 0); }
+          | OUTLINESTYLE STRING  { $$ = mkAttr (0, OUTLINESTYLE, STRING, $2, 0); }
+          | OUTLINEWIDTH INTEGER  { $$ = mkAttr (0, OUTLINEWIDTH, INTEGER, $2, 0); }
+          | WIDTH INTEGER  { $$ = mkAttr (0, OUTLINEWIDTH, INTEGER, $2, 0); }
+          | STYLE STRING  { $$ = mkAttr (0, STYLE, STRING, $2, 0); }
+          | LINE attrlist  { $$ = mkAttr (0, LINE, LIST, 0, $2); }
+          | POINT attrlist  { $$ = mkAttr (0, POINT, LIST, 0, $2); }
+          | TEXT STRING  { $$ = mkAttr (0, TEXT, STRING, $2, 0); }
+          | FONTNAME STRING  { $$ = mkAttr (0, FONTNAME, STRING, $2, 0); }
+          | FONTSIZE INTEGER  { $$ = mkAttr (0, FONTNAME, INTEGER, $2, 0); }
+          | COLOR STRING  { $$ = mkAttr (0, COLOR, STRING, $2, 0); }
+          ;
+
+%%
+
+static void deparseList (Dt_t* alist, agxbuf* xb); /* forward declaration */
+
+static void
+deparseAttr (gmlattr* ap, agxbuf* xb)
+{
+    if (ap->kind == LIST) {
+       agxbput (xb, ap->name);
+       agxbputc (xb, ' ');
+       deparseList (ap->u.lp, xb);
+    }
+    else if (ap->kind == STRING) {
+       agxbput (xb, ap->name);
+       agxbput (xb, " \"");
+       agxbput (xb, ap->u.value);
+       agxbput (xb, "\"");
+    }
+    else {
+       agxbput (xb, ap->name);
+       agxbputc (xb, ' ');
+       agxbput (xb, ap->u.value);
+    }
+}
+
+static void
+deparseList (Dt_t* alist, agxbuf* xb)
+{
+    gmlattr* ap;
+
+    agxbput (xb, "[ "); 
+    for (ap = dtfirst(alist); ap; ap = dtnext (alist, ap)) {
+       deparseAttr (ap, xb);
+       agxbputc (xb, ' ');
+    }
+    agxbput (xb, "]"); 
+  
+}
+
+static void
+unknown (Agobj_t* obj, gmlattr* ap, agxbuf* xb)
+{
+    char* str;
+
+    if (ap->kind == LIST) {
+       deparseList (ap->u.lp, xb);
+       str = agxbuse (xb);
+    }
+    else
+       str = ap->u.value;
+
+    agsafeset (obj, ap->name, str, "");
+}
+
+static void
+addNodeLabelGraphics (Agnode_t* np, Dt_t* alist, agxbuf* xb, agxbuf* unk)
+{
+    gmlattr* ap;
+    int cnt = 0;
+
+    for (ap = dtfirst(alist); ap; ap = dtnext (alist, ap)) {
+       if (ap->sort == TEXT) {
+           agsafeset (np, "label", ap->u.value, "");
+       }
+       else if (ap->sort == COLOR) {
+           agsafeset (np, "fontcolor", ap->u.value, "");
+       }
+       else if (ap->sort == FONTSIZE) {
+           agsafeset (np, "fontsize", ap->u.value, "");
+       }
+       else if (ap->sort == FONTNAME) {
+           agsafeset (np, "fontname", ap->u.value, "");
+       }
+       else {
+           if (cnt)
+               agxbputc (unk, ' '); 
+           else {
+               agxbput (unk, "[ "); 
+           }
+           deparseAttr (ap, unk);
+           cnt++;
+       }
+    }
+
+    if (cnt) {
+       agxbput (unk, " ]"); 
+       agsafeset (np, "LabelGraphics", agxbuse (unk), "");
+    }
+    else
+       agxbclear (unk); 
+}
+
+static void
+addEdgeLabelGraphics (Agedge_t* ep, Dt_t* alist, agxbuf* xb, agxbuf* unk)
+{
+    gmlattr* ap;
+    char* x = "0";
+    char* y = "0";
+    int cnt = 0;
+
+    for (ap = dtfirst(alist); ap; ap = dtnext (alist, ap)) {
+       if (ap->sort == TEXT) {
+           agsafeset (ep, "label", ap->u.value, "");
+       }
+       else if (ap->sort == COLOR) {
+           agsafeset (ep, "fontcolor", ap->u.value, "");
+       }
+       else if (ap->sort == FONTSIZE) {
+           agsafeset (ep, "fontsize", ap->u.value, "");
+       }
+       else if (ap->sort == FONTNAME) {
+           agsafeset (ep, "fontname", ap->u.value, "");
+       }
+       else if (ap->sort == XVAL) {
+           x = ap->u.value;
+       }
+       else if (ap->sort == YVAL) {
+           y = ap->u.value;
+       }
+       else {
+           if (cnt)
+               agxbputc (unk, ' '); 
+           else {
+               agxbput (unk, "[ "); 
+           }
+           deparseAttr (ap, unk);
+           cnt++;
+       }
+    }
+
+    agxbput (xb, x);
+    agxbputc (xb, ',');
+    agxbput (xb, y);
+    agsafeset (ep, "lp", agxbuse (xb), "");
+
+    if (cnt) {
+       agxbput (unk, " ]"); 
+       agsafeset (ep, "LabelGraphics", agxbuse (unk), "");
+    }
+    else
+       agxbclear (unk); 
+}
+
+static void
+addNodeGraphics (Agnode_t* np, Dt_t* alist, agxbuf* xb, agxbuf* unk)
+{
+    gmlattr* ap;
+    char* x = "0";
+    char* y = "0";
+    char buf[BUFSIZ];
+    double d;
+    int cnt = 0;
+
+    for (ap = dtfirst(alist); ap; ap = dtnext (alist, ap)) {
+       if (ap->sort == XVAL) {
+           x = ap->u.value;
+       }
+       else if (ap->sort == YVAL) {
+           y = ap->u.value;
+       }
+       else if (ap->sort == WVAL) {
+           d = atof (ap->u.value);
+           sprintf (buf, "%.04f", d/72.0);
+           agsafeset (np, "width", buf, "");
+       }
+       else if (ap->sort == HVAL) {
+           d = atof (ap->u.value);
+           sprintf (buf, "%.04f", d/72.0);
+           agsafeset (np, "height", buf, "");
+       }
+       else if (ap->sort == TYPE) {
+           agsafeset (np, "shape", ap->u.value, "");
+       }
+       else if (ap->sort == FILL) {
+           agsafeset (np, "color", ap->u.value, "");
+       }
+       else if (ap->sort == OUTLINE) {
+           agsafeset (np, "pencolor", ap->u.value, "");
+       }
+       else if ((ap->sort == WIDTH) && (ap->sort == OUTLINEWIDTH )) {
+           agsafeset (np, "penwidth", ap->u.value, "");
+       }
+       else if ((ap->sort == OUTLINESTYLE) && (ap->sort == OUTLINEWIDTH )) {
+           agsafeset (np, "style", ap->u.value, "");
+       }
+       else {
+           if (cnt)
+               agxbputc (unk, ' '); 
+           else {
+               agxbput (unk, "[ "); 
+           }
+           deparseAttr (ap, unk);
+           cnt++;
+       }
+    }
+
+    agxbput (xb, x);
+    agxbputc (xb, ',');
+    agxbput (xb, y);
+    agsafeset (np, "pos", agxbuse (xb), "");
+
+    if (cnt) {
+       agxbput (unk, " ]"); 
+       agsafeset (np, "graphics", agxbuse (unk), "");
+    }
+    else
+       agxbclear (unk); 
+}
+
+static void
+addEdgePoint (Agedge_t* ep, Dt_t* alist, agxbuf* xb)
+{
+    gmlattr* ap;
+    char* x = "0";
+    char* y = "0";
+
+    for (ap = dtfirst(alist); ap; ap = dtnext (alist, ap)) {
+        if (ap->sort == XVAL) {
+           x = ap->u.value;
+       }
+       else if (ap->sort == YVAL) {
+           y = ap->u.value;
+       }
+       else {
+           fprintf (stderr, "non-X/Y field in point attribute");
+           unknown ((Agobj_t*)ep, ap, xb);
+       }
+    }
+
+    if (agxblen(xb)) agxbputc (xb, ' ');
+    agxbput (xb, x);
+    agxbputc (xb, ',');
+    agxbput (xb, y);
+}
+
+static void
+addEdgePos (Agedge_t* ep, Dt_t* alist, agxbuf* xb)
+{
+    gmlattr* ap;
+    
+
+    for (ap = dtfirst(alist); ap; ap = dtnext (alist, ap)) {
+       if (ap->sort == POINT) {
+           addEdgePoint (ep, ap->u.lp, xb);
+       }
+       else {
+           fprintf (stderr, "non-point field in line attribute");
+           unknown ((Agobj_t*)ep, ap, xb);
+       }
+    }
+    agsafeset (ep, "pos", agxbuse (xb), "");
+}
+
+static void
+addEdgeGraphics (Agedge_t* ep, Dt_t* alist, agxbuf* xb, agxbuf* unk)
+{
+    gmlattr* ap;
+    int cnt = 0;
+
+    for (ap = dtfirst(alist); ap; ap = dtnext (alist, ap)) {
+       if (ap->sort == WIDTH) {
+           agsafeset (ep, "penwidth", ap->u.value, "");
+       }
+       else if (ap->sort == STYLE) {
+           agsafeset (ep, "style", ap->u.value, "");
+       }
+       else if (ap->sort == FILL) {
+           agsafeset (ep, "color", ap->u.value, "");
+       }
+       else if (ap->sort == LINE) {
+           addEdgePos (ep, ap->u.lp, xb);
+       }
+       else {
+           if (cnt)
+               agxbputc (unk, ' '); 
+           else {
+               agxbput (unk, "[ "); 
+           }
+           deparseAttr (ap, unk);
+           cnt++;
+       }
+    }
+
+    if (cnt) {
+       agxbput (unk, " ]"); 
+       agsafeset (ep, "graphics", agxbuse (unk), "");
+    }
+    else
+       agxbclear(unk);
+}
+
+static void
+addAttrs (Agobj_t* obj, Dt_t* alist, agxbuf* xb, agxbuf* unk)
+{
+    gmlattr* ap;
+
+    for (ap = dtfirst(alist); ap; ap = dtnext (alist, ap)) {
+       if (ap->sort == GRAPHICS) {
+           if (AGTYPE(obj) == AGNODE)
+               addNodeGraphics ((Agnode_t*)obj, ap->u.lp, xb, unk);
+           else if (AGTYPE(obj) == AGEDGE)
+               addEdgeGraphics ((Agedge_t*)obj, ap->u.lp, xb, unk);
+           else
+               unknown (obj, ap, xb);
+       }
+       else if (ap->sort == LABELGRAPHICS) {
+           if (AGTYPE(obj) == AGNODE)
+               addNodeLabelGraphics ((Agnode_t*)obj, ap->u.lp, xb, unk);
+           else if (AGTYPE(obj) == AGEDGE)
+               addEdgeLabelGraphics ((Agedge_t*)obj, ap->u.lp, xb, unk);
+           else
+               unknown (obj, ap, xb);
+       }
+       else if ((ap->sort == LABEL) && (AGTYPE(obj) != AGRAPH)) {
+           agsafeset (obj, "name", ap->u.value, "");
+       }
+       else
+           unknown (obj, ap, xb);
+    }
+}
+
+static Agraph_t*
+mkGraph (gmlgraph* G, Agraph_t* parent, agxbuf* xb, agxbuf* unk)
+{
+    Agraph_t* g;
+    Agnode_t* n;
+    Agnode_t* h;
+    Agedge_t* e;
+    gmlnode*  np;
+    gmledge*  ep;
+    gmlgraph* gp;
+
+    if (parent) {
+       g = agsubg (parent, NULL, 1);
+    }
+    else if (G->directed >= 1)
+       g = agopen ("", Agdirected, 0);
+    else
+       g = agopen ("", Agundirected, 0);
+
+    if (!parent && L) {
+       addAttrs ((Agobj_t*)g, L, xb, unk);
+    } 
+    for (np = dtfirst(G->nodelist); np; np = dtnext (G->nodelist, np)) {
+       if (!np->id) {
+          fprintf (stderr, "node without an id attribute"); 
+          exit (1);
+        }
+       n = agnode (g, np->id, 1);
+       addAttrs ((Agobj_t*)n, np->attrlist, xb, unk);
+    }
+
+    for (ep = dtfirst(G->edgelist); ep; ep = dtnext (G->edgelist, ep)) {
+       if (!ep->source) {
+          fprintf (stderr, "edge without an source attribute"); 
+          exit (1);
+        }
+       if (!ep->target) {
+          fprintf (stderr, "node without an target attribute"); 
+          exit (1);
+        }
+       n = agnode (g, ep->source, 1);
+       h = agnode (g, ep->target, 1);
+       e = agedge (g, n, h, NULL, 1);
+       addAttrs ((Agobj_t*)e, ep->attrlist, xb, unk);
+    }
+    for (gp = dtfirst(G->graphlist); gp; gp = dtnext (G->graphlist, gp)) {
+       mkGraph (gp, g, xb, unk);
+    }
+
+    addAttrs ((Agobj_t*)g, G->attrlist, xb, unk);
+
+    return g;
+}
+
+Agraph_t*
+gml_to_gv (FILE* fp, int cnt, int* errors)
+{
+    Agraph_t* g;
+    agxbuf xb;
+    unsigned char buf[BUFSIZ];
+    agxbuf unk;
+    unsigned char unknownb[BUFSIZ];
+    int error;
+
+    if (cnt == 0)
+       initgmlscan(fp);
+    else
+       initgmlscan(0);
+               
+    initstk();
+    L = NULL;
+    pushAlist ();
+    gmlparse ();
+
+    error = gmlerrors();
+    *errors |= error;
+    if (!G || error)
+       g = NULL;
+    else {
+       agxbinit (&xb, BUFSIZ, buf);
+       agxbinit (&unk, BUFSIZ, unknownb);
+       g = mkGraph (G, NULL, &xb, &unk);
+       agxbfree (&xb);
+    }
+
+    cleanup ();
+
+    return g;
+}
+
diff --git a/cmd/tools/gmlscan.l b/cmd/tools/gmlscan.l
new file mode 100644 (file)
index 0000000..44d0c0f
--- /dev/null
@@ -0,0 +1,140 @@
+/* vim:set shiftwidth=4 ts=8: */
+
+%{
+#include <gml2gv.h>
+#include <gmlparse.h>
+
+#define GRAPH_EOF_TOKEN     '@'     /* lex class must be defined below */
+
+static int line_num = 1;
+static int errors;
+static FILE* Ifile;
+
+void initgmlscan(FILE *ifile) 
+{ 
+    if (ifile) {
+       Ifile = ifile; 
+       line_num = 1;
+    }
+    errors = 0;
+}
+
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+        if ((result = fread(buf, 1, max_size, Ifile)) < 0) \
+                YY_FATAL_ERROR( "input in flex scanner failed" )
+#endif
+
+/* buffer for arbitrary length strings (longer than BUFSIZ) */
+static char     *Sbuf,*Sptr,*Send;
+static void beginstr(void) 
+{
+    if (Sbuf == NULL) {
+       Sbuf = malloc(BUFSIZ);
+       Send = Sbuf + BUFSIZ;
+    }
+    Sptr = Sbuf;
+    *Sptr = 0;
+}
+
+static void addstr(char *src) 
+{
+    char    c;
+    if (Sptr > Sbuf) Sptr--;
+    do {
+       do {c = *Sptr++ = *src++;} while (c && (Sptr < Send));
+       if (c) {
+           long    sz = Send - Sbuf;
+           long    off = Sptr - Sbuf;
+           sz *= 2;
+           Sbuf = (char*)realloc(Sbuf,sz);
+           Send = Sbuf + sz;
+           Sptr = Sbuf + off;
+       }
+    } while (c);
+}
+
+static void endstr(void) {
+    yylval.str = strdup(Sbuf);
+}
+
+%}
+GRAPH_EOF_TOKEN                         [@]
+ASCII     [ !#$%\047-\177]
+DIGIT     [0-9]
+L_INT     [-+]?{DIGIT}+
+MANTISSA  E[-+]?{DIGIT}
+L_REAL    [-+]?{DIGIT}*\.{DIGIT}*{MANTISSA}?
+L_ID      [a-zA-Z][a-zA-Z0-9]*
+%x qstring
+%%
+{GRAPH_EOF_TOKEN}           return(EOF);
+<INITIAL,qstring>\n         line_num++;
+^"#".*                      /* ignore # line */
+[ \t\r]                     /* ignore whitespace */
+
+"graph"                            return (GRAPH);
+"node"                      return (NODE);
+"edge"                      return (EDGE);
+"directed"                  return (DIRECTED);
+"id"                        return (ID);
+"source"                    return (SOURCE);
+"target"                    return (TARGET);
+"x"                         return (XVAL);
+"y"                         return (YVAL);
+"w"                         return (WVAL);
+"h"                         return (HVAL);
+"label"                     return (LABEL);
+"graphics"                  return (GRAPHICS);;
+"LabelGraphics"             return (LABELGRAPHICS);
+"type"                      return (TYPE);
+"fill"                      return (FILL);
+"outline"                   return (OUTLINE);
+"outlineStyle"              return (OUTLINESTYLE);
+"outlineWidth"              return (OUTLINEWIDTH);
+"width"                     return (WIDTH);
+"style"                     return (STYLE);
+"Line"                      return (LINE);
+"point"                     return (POINT);
+"text"                      return (TEXT);
+"fontSize"                  return (FONTSIZE);
+"fontName"                  return (FONTNAME);
+"color"                     return (COLOR);
+{L_INT}                     { yylval.str = strdup(yytext); return (INTEGER); }
+{L_REAL}                    { yylval.str = strdup(yytext); return (REAL); }
+{L_ID}                      { yylval.str = strdup(yytext); return (NAME); }
+["]                         BEGIN(qstring); beginstr();
+
+<qstring>["]                BEGIN(INITIAL); endstr(); return (STRING);
+<qstring>([^"]+)            addstr(yytext);
+
+.                           return (yytext[0]);
+
+%%
+
+void yyerror(char *str)
+{
+    char    buf[BUFSIZ];
+    if (errors)
+       return;
+    errors = 1;
+    sprintf(buf," %s in line %d near '%s'\n", str,line_num,yytext);
+    agerr(AGWARN,buf);
+}
+
+int gmlerrors()
+{
+    return errors;
+}
+void gmllexeof() { unput(GRAPH_EOF_TOKEN); }
+
+#ifndef YY_CALL_ONLY_ARG
+# define YY_CALL_ONLY_ARG void
+#endif
+
+int yywrap(YY_CALL_ONLY_ARG)
+{
+    return 1;
+}
+