/************************************************************************* * Copyright (c) 2011 AT&T Intellectual Property * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: Details at https://graphviz.org *************************************************************************/ #include #include #include #include #include "gv_channel.h" #define agfindattr(x, s) agattrsym(x, s) #define agraphattr(g, n, s) agattr(g, AGRAPH, n, s) #define agnodeattr(g, n, s) agattr(g, AGNODE, n, s) #define agedgeattr(g, n, s) agattr(g, AGEDGE, n, s) static char emptystring[] = {'\0'}; static GVC_t *gvc; static void gv_init(void) { // list of builtins, enable demand loading gvc = gvContextPlugins(lt_preloaded_symbols, DEMAND_LOADING); } Agraph_t *graph(char *name) { if (!gvc) gv_init(); return agopen(name, Agundirected, 0); } Agraph_t *digraph(char *name) { if (!gvc) gv_init(); return agopen(name, Agdirected, 0); } Agraph_t *strictgraph(char *name) { if (!gvc) gv_init(); return agopen(name, Agstrictundirected, 0); } Agraph_t *strictdigraph(char *name) { if (!gvc) gv_init(); return agopen(name, Agstrictdirected, 0); } Agraph_t *readstring(char *string) { if (!gvc) gv_init(); return agmemread(string); } Agraph_t *read(FILE *f) { if (!gvc) gv_init(); return agread(f, nullptr); } Agraph_t *read(const char *filename) { FILE *f = fopen(filename, "r"); if (!f) return nullptr; if (!gvc) gv_init(); Agraph_t *g = agread(f, nullptr); fclose(f); return g; } //------------------------------------------------- Agraph_t *graph(Agraph_t *g, char *name) { if (!gvc) gv_init(); return agsubg(g, name, 1); } Agnode_t *node(Agraph_t *g, char *name) { if (!gvc) return nullptr; return agnode(g, name, 1); } Agedge_t *edge(Agraph_t *g, Agnode_t *t, Agnode_t *h) { if (!gvc || !t || !h || !g) return nullptr; // edges from/to the protonode are not permitted if (AGTYPE(t) == AGRAPH || AGTYPE(h) == AGRAPH) return nullptr; return agedge(g, t, h, nullptr, 1); } Agedge_t *edge(Agnode_t *t, Agnode_t *h) { return edge(agraphof(t), t, h); } // induce tail if necessary Agedge_t *edge(char *tname, Agnode_t *h) { return edge(node(agraphof(h), tname), h); } // induce head if necessary Agedge_t *edge(Agnode_t *t, char *hname) { return edge(t, node(agraphof(t), hname)); } // induce tail/head if necessary Agedge_t *edge(Agraph_t *g, char *tname, char *hname) { return edge(g, node(g, tname), node(g, hname)); } //------------------------------------------------- static char *myagxget(void *obj, Agsym_t *a) { if (!obj || !a) return emptystring; char *val = agxget(obj, a); if (!val) return emptystring; if (strcmp(a->name, "label") == 0 && aghtmlstr(val)) { size_t len = strlen(val); auto hs = reinterpret_cast(malloc(len + 3)); hs[0] = '<'; strcpy(hs + 1, val); hs[len + 1] = '>'; hs[len + 2] = '\0'; return hs; } return val; } char *getv(Agraph_t *g, Agsym_t *a) { return myagxget(g, a); } char *getv(Agraph_t *g, char *attr) { if (!g || !attr) return nullptr; Agsym_t *a = agfindattr(agroot(g), attr); return myagxget(g, a); } static void myagxset(void *obj, Agsym_t *a, char *val) { if (strcmp(a->name, "label") == 0 && val[0] == '<') { size_t len = strlen(val); if (val[len - 1] == '>') { std::string hs(val + 1, len - 2); val = agstrdup_html(agraphof(obj), hs.c_str()); } } agxset(obj, a, val); } char *setv(Agraph_t *g, Agsym_t *a, char *val) { if (!g || !a || !val) return nullptr; myagxset(g, a, val); return val; } char *setv(Agraph_t *g, char *attr, char *val) { if (!g || !attr || !val) return nullptr; Agsym_t *a = agfindattr(agroot(g), attr); if (!a) a = agraphattr(g->root, attr, emptystring); myagxset(g, a, val); return val; } //------------------------------------------------- char *getv(Agnode_t *n, Agsym_t *a) { if (!n || !a) return nullptr; if (AGTYPE(n) == AGRAPH) // protonode return nullptr; // FIXME ?? return myagxget(n, a); } char *getv(Agnode_t *n, char *attr) { if (!n || !attr) return nullptr; if (AGTYPE(n) == AGRAPH) // protonode return nullptr; // FIXME ?? Agraph_t *g = agroot(agraphof(n)); Agsym_t *a = agattr(g, AGNODE, attr, nullptr); return myagxget(n, a); } char *setv(Agnode_t *n, Agsym_t *a, char *val) { if (!n || !a || !val) return nullptr; if (AGTYPE(n) == AGRAPH) // protonode return nullptr; // FIXME ?? myagxset(n, a, val); return val; } char *setv(Agnode_t *n, char *attr, char *val) { if (!n || !attr || !val) return nullptr; if (AGTYPE(n) == AGRAPH) { // protonode auto g = reinterpret_cast(n); (void)agattr(g, AGNODE, attr, val); // create default attribute in pseudo protonode // FIXME? - deal with html in "label" attributes return val; } Agraph_t *g = agroot(agraphof(n)); Agsym_t *a = agattr(g, AGNODE, attr, nullptr); if (!a) a = agnodeattr(g, attr, emptystring); myagxset(n, a, val); return val; } //------------------------------------------------- char *getv(Agedge_t *e, Agsym_t *a) { if (!e || !a) return nullptr; if (AGTYPE(e) == AGRAPH) // protoedge return nullptr; // FIXME ?? return myagxget(e, a); } char *getv(Agedge_t *e, char *attr) { if (!e || !attr) return nullptr; if (AGTYPE(e) == AGRAPH) // protoedge return nullptr; // FIXME ?? Agraph_t *g = agraphof(agtail(e)); Agsym_t *a = agattr(g, AGEDGE, attr, nullptr); return myagxget(e, a); } char *setv(Agedge_t *e, Agsym_t *a, char *val) { if (!e || !a || !val) return nullptr; if (AGTYPE(e) == AGRAPH) // protoedge return nullptr; // FIXME ?? myagxset(e, a, val); return val; } char *setv(Agedge_t *e, char *attr, char *val) { if (!e || !attr || !val) return nullptr; if (AGTYPE(e) == AGRAPH) { // protoedge auto g = reinterpret_cast(e); (void)agattr(g, AGEDGE, attr, val); // create default attribute in pseudo protoedge // FIXME? - deal with html in "label" attributes return val; } Agraph_t *g = agroot(agraphof(agtail(e))); Agsym_t *a = agattr(g, AGEDGE, attr, nullptr); if (!a) a = agattr(g, AGEDGE, attr, emptystring); myagxset(e, a, val); return val; } //------------------------------------------------- Agraph_t *findsubg(Agraph_t *g, char *name) { if (!g || !name) return nullptr; return agsubg(g, name, 0); } Agnode_t *findnode(Agraph_t *g, char *name) { if (!g || !name) return nullptr; return agnode(g, name, 0); } Agedge_t *findedge(Agnode_t *t, Agnode_t *h) { if (!t || !h) return nullptr; if (AGTYPE(t) == AGRAPH || AGTYPE(h) == AGRAPH) return nullptr; return agfindedge(agraphof(t), t, h); } Agsym_t *findattr(Agraph_t *g, char *name) { if (!g || !name) return nullptr; return agfindattr(g, name); } Agsym_t *findattr(Agnode_t *n, char *name) { if (!n || !name) return nullptr; return agfindattr(n, name); } Agsym_t *findattr(Agedge_t *e, char *name) { if (!e || !name) return nullptr; return agfindattr(e, name); } //------------------------------------------------- Agnode_t *headof(Agedge_t *e) { if (!e) return nullptr; if (AGTYPE(e) == AGRAPH) return nullptr; return aghead(e); } Agnode_t *tailof(Agedge_t *e) { if (!e) return nullptr; if (AGTYPE(e) == AGRAPH) return nullptr; return agtail(e); } Agraph_t *graphof(Agraph_t *g) { if (!g || g == g->root) return nullptr; return agroot(g); } Agraph_t *graphof(Agedge_t *e) { if (!e) return nullptr; if (AGTYPE(e) == AGRAPH) return reinterpret_cast( e); // graph of protoedge is itself recast return agraphof(agtail(e)); } Agraph_t *graphof(Agnode_t *n) { if (!n) return nullptr; if (AGTYPE(n) == AGRAPH) return reinterpret_cast( n); // graph of protonode is itself recast return agraphof(n); } Agraph_t *rootof(Agraph_t *g) { if (!g) return nullptr; return agroot(g); } //------------------------------------------------- Agnode_t *protonode(Agraph_t *g) { return reinterpret_cast(g); // gross abuse of the type system! } Agedge_t *protoedge(Agraph_t *g) { return reinterpret_cast(g); // gross abuse of the type system! } //------------------------------------------------- char *nameof(Agraph_t *g) { if (!g) return nullptr; return agnameof(g); } char *nameof(Agnode_t *n) { if (!n) return nullptr; if (AGTYPE(n) == AGRAPH) return nullptr; return agnameof(n); } char *nameof(Agsym_t *a) { if (!a) return nullptr; return a->name; } //------------------------------------------------- bool ok(Agraph_t *g) { return g != nullptr; } bool ok(Agnode_t *n) { return n != nullptr; } bool ok(Agedge_t *e) { return e != nullptr; } bool ok(Agsym_t *a) { return a != nullptr; } //------------------------------------------------- Agraph_t *firstsubg(Agraph_t *g) { if (!g) return nullptr; return agfstsubg(g); } Agraph_t *nextsubg(Agraph_t *g, Agraph_t *sg) { if (!g || !sg) return nullptr; return agnxtsubg(sg); } Agraph_t *firstsupg(Agraph_t *g) { return g->parent; } Agraph_t *nextsupg(Agraph_t *, Agraph_t *) { return nullptr; } Agedge_t *firstout(Agraph_t *g) { if (!g) return nullptr; for (Agnode_t *n = agfstnode(g); n; n = agnxtnode(g, n)) { Agedge_t *e = agfstout(g, n); if (e) return e; } return nullptr; } Agedge_t *nextout(Agraph_t *g, Agedge_t *e) { if (!g || !e) return nullptr; Agedge_t *ne = agnxtout(g, e); if (ne) return (ne); for (Agnode_t *n = agnxtnode(g, agtail(e)); n; n = agnxtnode(g, n)) { ne = agfstout(g, n); if (ne) return ne; } return nullptr; } Agedge_t *firstedge(Agraph_t *g) { return firstout(g); } Agedge_t *nextedge(Agraph_t *g, Agedge_t *e) { return nextout(g, e); } Agedge_t *firstout(Agnode_t *n) { if (!n) return nullptr; return agfstout(agraphof(n), n); } Agedge_t *nextout(Agnode_t *n, Agedge_t *e) { if (!n || !e) return nullptr; return agnxtout(agraphof(n), e); } Agnode_t *firsthead(Agnode_t *n) { if (!n) return nullptr; Agedge_t *e = agfstout(agraphof(n), n); if (!e) return nullptr; return aghead(e); } Agnode_t *nexthead(Agnode_t *n, Agnode_t *h) { if (!n || !h) return nullptr; Agraph_t *g = agraphof(n); Agedge_t *e = agfindedge(g, n, h); if (!e) return nullptr; do { e = agnxtout(g, AGMKOUT(e)); if (!e) return nullptr; } while (aghead(e) == h); return aghead(e); } Agedge_t *firstedge(Agnode_t *n) { if (!n) return nullptr; return agfstedge(agraphof(n), n); } Agedge_t *nextedge(Agnode_t *n, Agedge_t *e) { if (!n || !e) return nullptr; return agnxtedge(agraphof(n), e, n); } Agedge_t *firstin(Agraph_t *g) { if (!g) return nullptr; Agnode_t *n = agfstnode(g); if (!n) return nullptr; return agfstin(g, n); } Agedge_t *nextin(Agraph_t *g, Agedge_t *e) { if (!g || !e) return nullptr; Agedge_t *ne = agnxtin(g, e); if (ne) return (ne); Agnode_t *n = agnxtnode(g, aghead(e)); if (!n) return nullptr; return agfstin(g, n); } Agedge_t *firstin(Agnode_t *n) { if (!n) return nullptr; return agfstin(agraphof(n), n); } Agedge_t *nextin(Agnode_t *n, Agedge_t *e) { if (!n || !e) return nullptr; return agnxtin(agraphof(n), e); } Agnode_t *firsttail(Agnode_t *n) { if (!n) return nullptr; Agedge_t *e = agfstin(agraphof(n), n); if (!e) return nullptr; return agtail(e); } Agnode_t *nexttail(Agnode_t *n, Agnode_t *t) { if (!n || !t) return nullptr; Agraph_t *g = agraphof(n); Agedge_t *e = agfindedge(g, t, n); if (!e) return nullptr; do { e = agnxtin(g, AGMKIN(e)); if (!e) return nullptr; } while (agtail(e) == t); return agtail(e); } Agnode_t *firstnode(Agraph_t *g) { if (!g) return nullptr; return agfstnode(g); } Agnode_t *nextnode(Agraph_t *g, Agnode_t *n) { if (!g || !n) return nullptr; return agnxtnode(g, n); } Agnode_t *firstnode(Agedge_t *e) { if (!e) return nullptr; return agtail(e); } Agnode_t *nextnode(Agedge_t *e, Agnode_t *n) { if (!e || n != agtail(e)) return nullptr; return aghead(e); } Agsym_t *firstattr(Agraph_t *g) { if (!g) return nullptr; g = agroot(g); return agnxtattr(g, AGRAPH, nullptr); } Agsym_t *nextattr(Agraph_t *g, Agsym_t *a) { if (!g || !a) return nullptr; g = agroot(g); return agnxtattr(g, AGRAPH, a); } Agsym_t *firstattr(Agnode_t *n) { if (!n) return nullptr; Agraph_t *g = agraphof(n); return agnxtattr(g, AGNODE, nullptr); } Agsym_t *nextattr(Agnode_t *n, Agsym_t *a) { if (!n || !a) return nullptr; Agraph_t *g = agraphof(n); return agnxtattr(g, AGNODE, a); } Agsym_t *firstattr(Agedge_t *e) { if (!e) return nullptr; Agraph_t *g = agraphof(agtail(e)); return agnxtattr(g, AGEDGE, nullptr); } Agsym_t *nextattr(Agedge_t *e, Agsym_t *a) { if (!e || !a) return nullptr; Agraph_t *g = agraphof(agtail(e)); return agnxtattr(g, AGEDGE, a); } bool rm(Agraph_t *g) { if (!g) return false; // The rm function appears to have the semantics of agclose, so // we should just do that, and let cgraph take care of all the // details. agclose(g); return true; } bool rm(Agnode_t *n) { if (!n) return false; // removal of the protonode is not permitted if (strcmp(agnameof(n), "\001proto") == 0) return false; agdelete(agraphof(n), n); return true; } bool rm(Agedge_t *e) { if (!e) return false; // removal of the protoedge is not permitted if (strcmp(agnameof(aghead(e)), "\001proto") == 0 || strcmp(agnameof(agtail(e)), "\001proto") == 0) return false; agdelete(agroot(agraphof(aghead(e))), e); return true; } bool layout(Agraph_t *g, const char *engine) { if (!g) return false; (void)gvFreeLayout(gvc, g); // ignore errors int err = gvLayout(gvc, g, engine); return err == 0; } // annotate the graph with layout information bool render(Agraph_t *g) { if (!g) return false; attach_attrs(g); return true; } // render to stdout bool render(Agraph_t *g, const char *format) { if (!g) return false; int err = gvRender(gvc, g, format, stdout); return err == 0; } // render to an open FILE bool render(Agraph_t *g, const char *format, FILE *f) { if (!g) return false; int err = gvRender(gvc, g, format, f); return err == 0; } // render to an open channel bool renderchannel(Agraph_t *g, const char *format, const char *channelname) { if (!g) return false; gv_channel_writer_init(gvc); int err = gvRender(gvc, g, format, (FILE *)channelname); gv_writer_reset(gvc); // Reset to default return err == 0; } // render to a filename bool render(Agraph_t *g, const char *format, const char *filename) { if (!g) return false; int err = gvRenderFilename(gvc, g, format, filename); return err == 0; } typedef struct { char *data; int sz; // buffer size int len; // length of array } BA; // render to string result, using binding-dependent gv_string_writer() char *renderresult(Agraph_t *g, const char *format) { if (!g) return nullptr; if (!GD_alg(g)) return nullptr; BA ba; ba.sz = BUFSIZ; // must be freed by wrapper code ba.data = reinterpret_cast(malloc(ba.sz * sizeof(char))); ba.len = 0; gv_string_writer_init(gvc); (void)gvRender(gvc, g, format, reinterpret_cast(&ba)); gv_writer_reset(gvc); // Reset to default *reinterpret_cast(GD_alg(g)) = ba.len; return ba.data; } // render to string result, using binding-dependent gv_string_writer() void renderresult(Agraph_t *g, const char *format, char *outdata) { if (!g) return; gv_string_writer_init(gvc); (void)gvRender(gvc, g, format, reinterpret_cast(outdata)); gv_writer_reset(gvc); // Reset to default } // render to a malloc'ed data string, to be free'd by caller. char *renderdata(Agraph_t *g, const char *format) { if (!g) return nullptr; char *data; unsigned int length; int err = gvRenderData(gvc, g, format, &data, &length); if (err) return nullptr; return data; } bool write(Agraph_t *g, FILE *f) { if (!g) return false; int err = agwrite(g, f); return err == 0; } bool write(Agraph_t *g, const char *filename) { if (!g) return false; FILE *f = fopen(filename, "w"); if (!f) return false; int err = agwrite(g, f); fclose(f); return err == 0; } bool tred(Agraph_t *g) { if (!g) return false; int err = gvToolTred(g); return err == 0; }