|
Graphviz
2.31.20130522.0446
|
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 }
1.7.5