Graphviz  2.31.20130522.0446
lib/graph/graphio.c
Go to the documentation of this file.
00001 /* $Id$ $Revision$ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /*************************************************************************
00005  * Copyright (c) 2011 AT&T Intellectual Property 
00006  * All rights reserved. This program and the accompanying materials
00007  * are made available under the terms of the Eclipse Public License v1.0
00008  * which accompanies this distribution, and is available at
00009  * http://www.eclipse.org/legal/epl-v10.html
00010  *
00011  * Contributors: See CVS logs. Details at http://www.graphviz.org/
00012  *************************************************************************/
00013 
00014 
00015 #include "libgraph.h"
00016 
00017 typedef struct printdict_t {
00018     Dict_t *nodesleft, *edgesleft, *subgleft, *e_insubg, *n_insubg;
00019 } printdict_t;
00020 
00021 /*
00022  * memgets - same api as gets
00023  *
00024  * gets one line at a time from a memory buffer and places it in a user buffer
00025  *    up to a maximum of n characters
00026  *
00027  * returns pointer to obtained line in user buffer, or
00028  * returns NULL when last line read from memory buffer
00029  */
00030 static char *memgets(char *ubuf, int n, FILE * mbuf)
00031 {
00032     static char *mempos;
00033     char *to, *clp;             /* clp = current line pointer */
00034     int i;
00035 
00036     if (!n) {                   /* a call with n==0 (from aglexinit) resets */
00037         mempos = (char *) mbuf; /* cast from FILE* required by API */
00038     }
00039 
00040     clp = to = ubuf;
00041     for (i = 0; i < n - 1; i++) {       /* leave room for terminator */
00042         if (*mempos == '\0') {
00043             if (i) {            /* if mbuf doesn't end in \n, provide one */
00044                 *to++ = '\n';
00045             } else {            /* all done */
00046                 clp = NULL;
00047                 mempos = NULL;
00048             }
00049             break;              /* last line or end-of-buffer */
00050         }
00051         if (*mempos == '\n') {
00052             *to++ = *mempos++;
00053             break;              /* all done with this line */
00054         }
00055         *to++ = *mempos++;      /* copy character */
00056     }
00057     *to++ = '\0';               /* place terminator in ubuf */
00058     return clp;
00059 }
00060 
00061 static Agraph_t*
00062 finish (int rv, Agraph_t *g)
00063 {
00064     if (rv) {
00065         if (g) agclose (g);
00066         return NULL;
00067     }
00068     else
00069         return g;
00070 }
00071 
00072 Agraph_t *agread(FILE * fp)
00073 {
00074     int rv;
00075     aglexinit(fp, NULL);        /* use fgets from current io discipline */
00076     rv = agparse();
00077     return finish(rv, AG.parsed_g);
00078 }
00079 
00080 Agraph_t *agmemread(char *cp)
00081 {
00082     int rv;
00083     gets_f savefgets = AG.fgets;
00084  
00085     AG.fgets = memgets;  /* memgets defined above */
00086     /* cast cp into a file pointer */
00087     aglexinit((FILE *) cp, NULL);
00088     rv = agparse();
00089     AG.fgets = savefgets;
00090     return finish(rv, AG.parsed_g);
00091 }
00092 
00093 Agraph_t *agread_usergets(FILE * fp, gets_f usergets)
00094 {
00095     int rv;
00096     gets_f savefgets = AG.fgets;
00097 
00098     AG.fgets = usergets;                /* usergets provided externally */
00099     aglexinit(fp, NULL);
00100     rv = agparse();
00101     AG.fgets = savefgets;
00102     return finish(rv, AG.parsed_g);
00103 }
00104 
00105 static int
00106 _is_number_char(char c)
00107 {
00108     return (isdigit(c) || (c == '.'));
00109 }
00110 
00111 /* _agstrcanon:
00112  * Canonicalize an ordinary string if necessary.
00113  */
00114 static char* 
00115 _agstrcanon (char* arg, char* buf)
00116 {
00117     char *s = arg;
00118     unsigned char uc;
00119     char *p = buf;
00120     int cnt = 0, dotcnt = 0;
00121     int has_special = FALSE;
00122     int maybe_num;
00123     int backslash_pending = FALSE;
00124 
00125     if (ISEMPTYSTR(arg))
00126         return "\"\"";
00127     *p++ = '\"';
00128     uc = *(unsigned char *) s++;
00129     maybe_num = _is_number_char(uc) || (uc == '-');
00130     while (uc) {
00131         if (uc == '\"') {
00132             *p++ = '\\';
00133             has_special = TRUE;
00134         } 
00135         else if (maybe_num) {
00136             if (uc == '-') {
00137                 if (cnt) {
00138                     maybe_num = FALSE;
00139                     has_special = TRUE;
00140                 }
00141             }
00142             else if (uc == '.') {
00143                 if (dotcnt++) {
00144                     maybe_num = FALSE;
00145                     has_special = TRUE;
00146                 }
00147             }
00148             else if (!isdigit(uc)) {
00149                 maybe_num = FALSE;
00150                 has_special = TRUE;
00151             }
00152         }
00153         else if (!ISALNUM(uc))
00154             has_special = TRUE;
00155         *p++ = (char) uc;
00156         uc = *(unsigned char *) s++;
00157         cnt++;
00158         if (uc && backslash_pending && !((_is_number_char(p[-1]) || isalpha(p[-1]) || (p[-1] == '\\')) && (_is_number_char(uc) || isalpha(uc)))) {
00159             *p++ = '\\';
00160             *p++ = '\n';
00161             has_special = TRUE;
00162             backslash_pending = FALSE;
00163         } else if (uc && cnt % SMALLBUF == 0) {
00164             if (!((_is_number_char(p[-1]) || isalpha(p[-1]) || (p[-1] == '\\')) && (_is_number_char(uc) || isalpha(uc)))) {
00165                 *p++ = '\\';
00166                 *p++ = '\n';
00167                 has_special = TRUE;
00168             } else {
00169                 backslash_pending = TRUE;
00170             }
00171         }
00172     }
00173     *p++ = '\"';
00174     *p = '\0';
00175     if (has_special || ((cnt == 1) && ((*arg == '.') || (*arg == '-'))))
00176         return buf;
00177 
00178     /* use quotes to protect tokens (example, a node named "node") */
00179     if (agtoken(arg) >= 0)
00180         return buf;
00181     return arg;
00182 }
00183 
00184 /* agstrcanon:
00185  * handles both html and ordinary strings.
00186  * canonicalize a string for printing.
00187  * changes to the semantics of this function
00188  * also involve the string scanner in lexer.c
00189  * Unsafe if buf is not large enough.
00190  */
00191 char *agstrcanon(char *arg, char *buf)
00192 {
00193     char *s = arg;
00194     char *p = buf;
00195 
00196     if (aghtmlstr(arg)) {
00197         *p++ = '<';
00198         while (*s)
00199             *p++ = *s++;
00200         *p++ = '>';
00201         *p = '\0';
00202         return buf;
00203     }
00204     else
00205         return (_agstrcanon(arg, buf));
00206 }
00207 
00208 void agsetiodisc(
00209     char * (*myfgets) (char *s, int size, FILE *stream),
00210     size_t (*myfwrite) (const void *ptr, size_t size, size_t nmemb, FILE *stream),
00211     int (*myferror) (FILE *stream)
00212 )
00213 {
00214     if (myfgets) AG.fgets = myfgets;
00215     if (myfwrite) AG.fwrite = myfwrite;
00216 #if defined(__SUNPRO_C) || defined(__CYGWIN__) || defined(__MINGW32__) || defined(__FreeBSD__)
00217 #undef ferror
00218 #endif
00219     if (myferror) AG.ferror = myferror;
00220 }
00221 
00222 
00223 int agputs(const char *s, FILE *fp)
00224 {
00225     size_t len = strlen(s);
00226 
00227     if (AG.fwrite(s, sizeof(char), len, fp) != len) {
00228         return EOF;
00229     }
00230     return +1;
00231 }
00232 
00233 int agputc(int c, FILE *fp)
00234 {
00235     const char cc = c;
00236 
00237     if (AG.fwrite (&cc, sizeof(char), 1, fp) != 1) {
00238         return EOF;
00239     }
00240     return c;
00241 }
00242 
00243 static void tabover(FILE * fp, int tab)
00244 {
00245     while (tab--)
00246         agputc('\t', fp);
00247 }
00248 
00249 static char *getoutputbuffer(char *str)
00250 {
00251     static char *rv;
00252     static int len;
00253     int req;
00254 
00255     req = MAX(2 * strlen(str) + 2, BUFSIZ);
00256     if (req > len) {
00257         if (rv)
00258             rv = (char*)realloc(rv, req);
00259         else
00260             rv = (char*)malloc(req);
00261         len = req;
00262     }
00263     return rv;
00264 }
00265 
00266 /* agcanonical:
00267  * Safe version of agstrcanon.
00268  */
00269 char*
00270 agcanonical(char *str)
00271 {
00272     return agstrcanon(str, getoutputbuffer(str));
00273 }
00274 
00275 /* agcanon:
00276  * Safe version of agstrcanon but using only double quotes.
00277  * Any string can be used, but there is no check for html strings.
00278  */
00279 char*
00280 agcanon(char *str)
00281 {
00282     return _agstrcanon(str, getoutputbuffer(str));
00283 }
00284 
00285 static void write_dict(Agdict_t * dict, FILE * fp)
00286 {
00287     int i, cnt = 0;
00288     Agsym_t *a;
00289 
00290     for (i = 0; i < dtsize(dict->dict); i++) {
00291         a = dict->list[i];
00292         if (ISEMPTYSTR(a->value) == FALSE) {
00293             if (cnt++ == 0) {
00294                 agputc('\t', fp);
00295                 agputs(dict->name, fp);
00296                 agputs(" [", fp);
00297             }
00298             else {
00299                 agputs(", ", fp);
00300             }
00301             agputs(a->name, fp);
00302             agputc('=', fp);
00303             agputs(agcanonical(a->value), fp);
00304         }
00305     }
00306     if (cnt > 0)
00307         agputs("];\n", fp);
00308 }
00309 
00310 static void write_diffattr(FILE * fp, int indent, void *obj, void *par,
00311                            Agdict_t * dict)
00312 {
00313     Agsym_t *a;
00314     int i;
00315     char *p, *q;
00316     int cnt = 0;
00317 
00318     for (i = 0; i < dtsize(dict->dict); i++) {
00319         a = dict->list[i];
00320         if (a->printed == FALSE)
00321             continue;
00322         p = agxget(obj, a->index);
00323         if (par)
00324             q = agxget(par, a->index);
00325         else
00326             q = a->value;
00327         if (strcmp(p, q)) {
00328             if (cnt++ == 0) {
00329                 tabover(fp, indent);
00330                 agputs(dict->name, fp);
00331                 agputs(" [", fp);
00332             } else {
00333                 agputs(",\n", fp);
00334                 tabover(fp, indent + 1);
00335             }
00336             agputs(agcanonical(a->name), fp);
00337             agputc('=', fp);
00338             agputs(agcanonical(p), fp);
00339         }
00340     }
00341     if (cnt > 0)
00342         agputs("];\n", fp);
00343 }
00344 
00345 static void writeattr(FILE * fp, int *npp, char *name, char *val)
00346 {
00347     agputs(++(*npp) > 1 ? ", " : " [", fp);
00348     agputs(agcanonical(name), fp);
00349     agputc('=', fp);
00350     agputs(agcanonical(val), fp);
00351 }
00352 
00353 void agwrnode(Agraph_t * g, FILE * fp, Agnode_t * n, int full, int indent)
00354 {
00355     char *myval, *defval;
00356     int i, didwrite = FALSE;
00357     int nprint = 0;
00358     Agdict_t *d = n->graph->univ->nodeattr;
00359     Agsym_t *a;
00360 
00361     if (full) {
00362         for (i = 0; i < dtsize(d->dict); i++) {
00363             a = d->list[i];
00364             if (a->printed == FALSE)
00365                 continue;
00366             myval = agget(n, a->name);
00367             if (g == n->graph)
00368                 defval = a->value;
00369             else
00370                 defval = agget(g->proto->n, a->name);
00371             if (strcmp(defval, myval)) {
00372                 if (didwrite == FALSE) {
00373                     tabover(fp, indent);
00374                     agputs(agcanonical(n->name), fp);
00375                     didwrite = TRUE;
00376                 }
00377                 writeattr(fp, &nprint, a->name, myval);
00378             }
00379         }
00380         if (didwrite) {
00381             agputs(nprint > 0 ? "];\n" : ";\n", fp);
00382             return;
00383         }
00384     }
00385     if ((agfstout(g, n) == NULL) && (agfstin(g, n) == NULL)) {
00386         tabover(fp, indent);
00387         agputs(agcanonical(n->name), fp);
00388         agputs(";\n", fp);
00389     }
00390 }
00391 
00392 /* writenodeandport:
00393  */
00394 static void writenodeandport(FILE * fp, char *node, char *port)
00395 {
00396     char *ss;
00397     agputs(agcanonical(node), fp);      /* slimey i know */
00398     if (port && *port) {
00399         if (aghtmlstr(port)) {
00400             agputc(':', fp);
00401             agputs(agstrcanon(port, getoutputbuffer(port)), fp);
00402         }
00403         else {
00404             ss = strchr (port, ':');
00405             if (ss) {
00406                 *ss = '\0';
00407                 agputc(':', fp);
00408                 agputs(_agstrcanon(port, getoutputbuffer(port)), fp);
00409                 agputc(':', fp);
00410                 agputs(_agstrcanon(ss+1, getoutputbuffer(ss+1)), fp);
00411                 *ss = ':';
00412             }
00413             else {
00414                 agputc(':', fp);
00415                 agputs(_agstrcanon(port, getoutputbuffer(port)), fp);
00416             }
00417         }
00418     }
00419 }
00420 
00421 void agwredge(Agraph_t * g, FILE * fp, Agedge_t * e, int list_all)
00422 {
00423     char *myval, *defval, *tport, *hport;
00424     int i, nprint = 0;
00425     Agdict_t *d = e->tail->graph->univ->edgeattr;
00426     Agsym_t *a;
00427 
00428     if (e->attr) {
00429         tport = e->attr[TAILX];
00430         hport = e->attr[HEADX];
00431     }
00432     else tport = hport = "";
00433     writenodeandport(fp, e->tail->name, tport);
00434     agputs(((g->kind & AGFLAG_DIRECTED) ? " -> " : " -- "), fp);
00435     writenodeandport(fp, e->head->name, hport);
00436     if (list_all) {
00437         for (i = 0; i < dtsize(d->dict); i++) {
00438             a = d->list[i];
00439             if ((a->printed == FALSE)
00440                 || ((i == KEYX) && (e->printkey != MUSTPRINT)))
00441                 continue;
00442             myval = agget(e, a->name);
00443             if (g == g->root)
00444                 defval = a->value;
00445             else
00446                 defval = agget(g->proto->e, a->name);
00447             if (strcmp(defval, myval))
00448                 writeattr(fp, &nprint, a->name, myval);
00449         }
00450     }
00451     agputs(nprint > 0 ? "];\n" : ";\n", fp);
00452 }
00453 
00454 Dtdisc_t agEdgedisc = {
00455     offsetof(Agedge_t, id),
00456     sizeof(int),
00457     -1,
00458     NIL(Dtmake_f),
00459     NIL(Dtfree_f),
00460     (Dtcompar_f) agcmpid,
00461     NIL(Dthash_f),
00462     NIL(Dtmemory_f),
00463     NIL(Dtevent_f)
00464 };
00465 static void
00466 write_subg(Agraph_t * g, FILE * fp, Agraph_t * par, int indent,
00467            printdict_t * state)
00468 {
00469     Agraph_t *subg, *meta;
00470     Agnode_t *n, *pn;
00471     Agedge_t *e, *pe;
00472     Dict_t *save_e, *save_n;
00473 
00474     if (indent) {
00475         tabover(fp, indent++);
00476         if (dtsearch(state->subgleft, g->meta_node)) {
00477             if (strncmp(g->name, "_anonymous", 10)) {
00478                 agputs("subgraph ", fp);
00479                 agputs(agcanonical(g->name), fp);
00480                 agputs(" {\n", fp);
00481             }
00482             else {
00483                 agputs("{\n", fp);      /* no name printed for anonymous subg */
00484             }
00485             write_diffattr(fp, indent, g, par, g->univ->globattr);
00486             /* The root node and edge environment use the dictionaries,
00487              * not the proto node or edge, so the next level down must
00488              * record differences with the dictionaries.
00489              */
00490             if (par == g->root) {
00491                 pn = NULL;
00492                 pe = NULL;
00493             } else {
00494                 pn = par->proto->n;
00495                 pe = par->proto->e;
00496             }
00497             write_diffattr(fp, indent, g->proto->n, pn, g->univ->nodeattr);
00498             write_diffattr(fp, indent, g->proto->e, pe, g->univ->edgeattr);
00499             dtdelete(state->subgleft, g->meta_node);
00500         } else {
00501             agputs("subgraph ", fp);
00502             agputs(agcanonical(g->name), fp);
00503             agputs(";\n", fp);
00504             return;
00505         }
00506     } else
00507         write_diffattr(fp, ++indent, g, NULL, g->univ->globattr);
00508 
00509     save_n = state->n_insubg;
00510     save_e = state->e_insubg;
00511     meta = g->meta_node->graph;
00512     state->n_insubg = dtopen(&agNamedisc, Dttree);
00513     state->e_insubg = dtopen(&agOutdisc, Dttree);
00514     for (e = agfstout(meta, g->meta_node); e; e = agnxtout(meta, e)) {
00515         subg = agusergraph(e->head);
00516         write_subg(subg, fp, g, indent, state);
00517     }
00518     for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
00519         if (dtsearch(state->nodesleft, n)) {
00520             agwrnode(g, fp, n, TRUE, indent);
00521             dtdelete(state->nodesleft, n);
00522         } else {
00523             if (dtsearch(state->n_insubg, n) == NULL) {
00524                 agwrnode(g, fp, n, FALSE, indent);
00525             }
00526         }
00527         dtinsert(save_n, n);
00528     }
00529 
00530     dtdisc(g->outedges, &agEdgedisc, 0);        /* sort by id */
00531     for (e = (Agedge_t *) dtfirst(g->outedges); e;
00532          e = (Agedge_t *) dtnext(g->outedges, e)) {
00533         if (dtsearch(state->edgesleft, e)) {
00534             tabover(fp, indent);
00535             agwredge(g, fp, e, TRUE);
00536             dtdelete(state->edgesleft, e);
00537         } else {
00538             if (dtsearch(state->e_insubg, e) == NULL) {
00539                 tabover(fp, indent);
00540                 agwredge(g, fp, e, FALSE);
00541             }
00542         }
00543         dtinsert(save_e, e);
00544     }
00545     dtdisc(g->outedges, &agOutdisc, 0); /* sort by name */
00546     dtclose(state->n_insubg);
00547     state->n_insubg = save_n;
00548     dtclose(state->e_insubg);
00549     state->e_insubg = save_e;
00550 
00551     if (indent > 1) {
00552         tabover(fp, indent - 1);
00553         agputs("}\n", fp);
00554     }
00555 }
00556 
00557 static Dict_t *Copy;
00558 static int copydictf(Dict_t * d, void *a, void *ignored)
00559 {
00560     dtinsert(Copy, (Agsym_t *) a);
00561     return 0;
00562 }
00563 
00564 static void copydict(Dict_t * from, Dict_t * to)
00565 {
00566     Copy = to;
00567     dtwalk(from, copydictf, 0);
00568 }
00569 
00570 static printdict_t *new_printdict_t(Agraph_t * g)
00571 {
00572     printdict_t *rv = NEW(printdict_t);
00573     rv->nodesleft = dtopen(&agNodedisc, Dttree);
00574     copydict(g->nodes, rv->nodesleft);
00575     rv->edgesleft = dtopen(&agEdgedisc, Dttree);
00576     copydict(g->outedges, rv->edgesleft);
00577     rv->n_insubg = dtopen(&agNodedisc, Dttree);
00578     rv->e_insubg = dtopen(&agOutdisc, Dttree);
00579     rv->subgleft = dtopen(&agNodedisc, Dttree);
00580     copydict(g->meta_node->graph->nodes, rv->subgleft);
00581     return rv;
00582 }
00583 
00584 static void free_printdict_t(printdict_t * dict)
00585 {
00586     dtclose(dict->nodesleft);
00587     dtclose(dict->n_insubg);
00588     dtclose(dict->edgesleft);
00589     dtclose(dict->e_insubg);
00590     dtclose(dict->subgleft);
00591     free(dict);
00592 }
00593 
00594 #ifdef ferror
00595 /* if ferror is a macro (__SUNPRO_C __CYGWIN__ __MINGW32__ __FreeBSD__ and poss others)
00596  * then wrap it in a function */
00597 static int agferror(FILE *stream)
00598 {
00599         return ferror(stream);
00600 }
00601 #endif
00602 
00603 
00604 int agwrite(Agraph_t * g, FILE * fp)
00605 {
00606     printdict_t *p;
00607 
00608     if (AG.fwrite == NULL) {
00609         AG.fwrite = fwrite;   /* init to system version of fwrite() */
00610     }
00611     if (AG.ferror == NULL) {
00612 #ifdef ferror
00613 #undef ferror
00614         /* if ferror is a macro, then use our wrapper function, but 
00615          * undef the macro first so it doesn't subst in "AG.ferror" */
00616         AG.ferror = agferror; /* init to ferror macro wrapper function */
00617 #else
00618         AG.ferror = ferror;   /* init to system version of ferror() */
00619 #endif
00620     }
00621 
00622     /* write the graph header */
00623     agputs((AG_IS_STRICT(g)) ? "strict " : "", fp);
00624     agputs((AG_IS_DIRECTED(g)) ? "digraph" : "graph", fp);
00625     if (strncmp(g->name, "_anonymous", 10)) {
00626         agputc(' ', fp);
00627         agputs(agcanonical(g->name), fp);
00628     }
00629     agputs(" {\n", fp);
00630 
00631     /* write the top level attribute defs */
00632     write_dict(g->univ->globattr, fp);
00633     write_dict(g->univ->nodeattr, fp);
00634     write_dict(g->univ->edgeattr, fp);
00635 
00636     /* write the graph contents */
00637     p = new_printdict_t(g);
00638     write_subg(g, fp, (Agraph_t *) 0, 0, p);
00639     agputs("}\n", fp);
00640     free_printdict_t(p);
00641     return AG.ferror(fp);
00642 }