123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666 |
- /**
- * @file
- * @brief <a href=https://en.wikipedia.org/wiki/GXL>GXL</a>-DOT convert subroutines
- */
- /*************************************************************************
- * 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
- * https://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors: Details at https://graphviz.org
- *************************************************************************/
- #include <assert.h>
- #include "convert.h"
- #include <cgraph/gv_ctype.h>
- #include <cgraph/list.h>
- #include <stdbool.h>
- #include <stdio.h>
- #include <util/agxbuf.h>
- #include <util/alloc.h>
- #include <util/exit.h>
- #include <util/startswith.h>
- #include <util/unreachable.h>
- #ifdef HAVE_EXPAT
- #include <expat.h>
- #include <limits.h>
- #include <stdlib.h>
- #ifndef XML_STATUS_ERROR
- #define XML_STATUS_ERROR 0
- #endif
- #define NAMEBUF 100
- #define GXL_ATTR "_gxl_"
- #define GXL_ID "_gxl_id"
- #define GXL_ROLE "_gxl_role"
- #define GXL_HYPER "_gxl_hypergraph"
- #define GXL_FROM "_gxl_fromorder"
- #define GXL_TO "_gxl_toorder"
- #define GXL_TYPE "_gxl_type"
- #define GXL_COMP "_gxl_composite_"
- #define GXL_LOC "_gxl_locator_"
- typedef enum {
- TAG_NONE,
- TAG_GRAPH,
- TAG_NODE,
- TAG_EDGE,
- TAG_HTML_LIKE_STRING,
- } attr_t;
- typedef struct {
- agxbuf xml_attr_name;
- agxbuf xml_attr_value;
- agxbuf composite_buffer;
- bool listen;
- attr_t closedElementType;
- attr_t globalAttrType;
- bool compositeReadState;
- bool edgeinverted;
- Dt_t *nameMap;
- } userdata_t;
- static Agraph_t *root; /* root graph */
- static attr_t Current_class; /* Current element type */
- static Agraph_t *G; /* Current graph */
- static Agnode_t *N; /* Set if Current_class == TAG_NODE */
- static Agedge_t *E; /* Set if Current_class == TAG_EDGE */
- DEFINE_LIST(graph_stack, Agraph_t *)
- static graph_stack_t Gstack;
- typedef struct {
- Dtlink_t link;
- char *name;
- char *unique_name;
- } namev_t;
- static void *make_nitem(void *p, Dtdisc_t *disc){
- (void)disc;
- namev_t *objp = p;
- namev_t *np = malloc(sizeof(namev_t));
- if (np == NULL)
- return NULL;
- np->name = objp->name;
- np->unique_name = 0;
- return np;
- }
- static void free_nitem(void *name) {
- namev_t *np = name;
- free(np->unique_name);
- free(np);
- }
- static Dtdisc_t nameDisc = {
- .key = offsetof(namev_t, name),
- .size = -1,
- .link = offsetof(namev_t, link),
- .makef = make_nitem,
- .freef = free_nitem,
- };
- static userdata_t genUserdata(void) {
- userdata_t user = {0};
- user.listen = false;
- user.closedElementType = TAG_NONE;
- user.globalAttrType = TAG_NONE;
- user.compositeReadState = false;
- user.edgeinverted = false;
- user.nameMap = dtopen(&nameDisc, Dtoset);
- return user;
- }
- static void freeUserdata(userdata_t ud) {
- dtclose(ud.nameMap);
- agxbfree(&ud.xml_attr_name);
- agxbfree(&ud.xml_attr_value);
- agxbfree(&ud.composite_buffer);
- }
- static void addToMap(Dt_t * map, char *name, char *uniqueName)
- {
- namev_t obj;
- namev_t *objp;
- obj.name = name;
- objp = dtinsert(map, &obj);
- assert(objp->unique_name == 0);
- objp->unique_name = gv_strdup(uniqueName);
- }
- static char *mapLookup(Dt_t *nm, const char *name) {
- namev_t *objp = dtmatch(nm, name);
- if (objp)
- return objp->unique_name;
- else
- return 0;
- }
- static int isAnonGraph(const char *name)
- {
- if (*name++ != '%')
- return 0;
- while (gv_isdigit(*name))
- name++; /* skip over digits */
- return (*name == '\0');
- }
- static void push_subg(Agraph_t * g)
- {
- // insert the new graph
- graph_stack_push_back(&Gstack, g);
- // save the root if this is the first graph
- if (graph_stack_size(&Gstack) == 1) {
- root = g;
- }
- // update the top graph
- G = g;
- }
- static Agraph_t *pop_subg(void)
- {
- // is the stack empty?
- if (graph_stack_is_empty(&Gstack)) {
- fprintf(stderr, "gxl2gv: Gstack underflow in graph parser\n");
- graphviz_exit(EXIT_FAILURE);
- }
- // pop the top graph
- Agraph_t *g = graph_stack_pop_back(&Gstack);
- // update the top graph
- if (!graph_stack_is_empty(&Gstack))
- G = *graph_stack_back(&Gstack);
- return g;
- }
- static Agnode_t *bind_node(const char *name)
- {
- N = agnode(G, (char *) name, 1);
- return N;
- }
- static Agedge_t *bind_edge(const char *tail, const char *head)
- {
- Agnode_t *tailNode, *headNode;
- char *key = 0;
- tailNode = agnode(G, (char *) tail, 1);
- headNode = agnode(G, (char *) head, 1);
- E = agedge(G, tailNode, headNode, key, 1);
- return E;
- }
- static int get_xml_attr(char *attrname, const char **atts)
- {
- int count = 0;
- while (atts[count] != NULL) {
- if (strcmp(attrname, atts[count]) == 0) {
- return count + 1;
- }
- count += 2;
- }
- return -1;
- }
- static void setName(Dt_t * names, Agobj_t * n, char *value)
- {
- Agsym_t *ap;
- char *oldName;
- ap = agattr(root, AGTYPE(n), GXL_ID, "");
- agxset(n, ap, agnameof(n));
- oldName = agxget(n, ap); /* set/get gives us new copy */
- addToMap(names, oldName, value);
- agrename(n, value);
- }
- static char *defval = "";
- static void
- setNodeAttr(Agnode_t * np, char *name, char *value, userdata_t * ud,
- bool is_html)
- {
- Agsym_t *ap;
- if (strcmp(name, "name") == 0) {
- setName(ud->nameMap, (Agobj_t *) np, value);
- } else {
- ap = agattr(root, AGNODE, name, 0);
- if (!ap)
- ap = agattr(root, AGNODE, name, defval);
- if (is_html) {
- char *val = agstrdup_html(root, value);
- agxset(np, ap, val);
- agstrfree(root, val); // drop the extra reference count we bumped for val
- } else {
- agxset(np, ap, value);
- }
- }
- }
- #define NODELBL "node:"
- #define NLBLLEN (sizeof(NODELBL)-1)
- #define EDGELBL "edge:"
- #define ELBLLEN (sizeof(EDGELBL)-1)
- /* setGlobalNodeAttr:
- * Set global node attribute.
- * The names must always begin with "node:".
- */
- static void
- setGlobalNodeAttr(Agraph_t * g, char *name, char *value)
- {
- if (!startswith(name, NODELBL))
- fprintf(stderr,
- "Warning: global node attribute %s in graph %s does not begin with the prefix %s\n",
- name, agnameof(g), NODELBL);
- else
- name += NLBLLEN;
- if ((g != root) && !agattr(root, AGNODE, name, 0))
- agattr(root, AGNODE, name, defval);
- agattr(G, AGNODE, name, value);
- }
- static void
- setEdgeAttr(Agedge_t * ep, char *name, char *value, userdata_t * ud,
- bool is_html)
- {
- Agsym_t *ap;
- char *attrname;
- if (strcmp(name, "headport") == 0) {
- if (ud->edgeinverted)
- attrname = "tailport";
- else
- attrname = "headport";
- ap = agattr(root, AGEDGE, attrname, 0);
- if (!ap)
- ap = agattr(root, AGEDGE, attrname, defval);
- } else if (strcmp(name, "tailport") == 0) {
- if (ud->edgeinverted)
- attrname = "headport";
- else
- attrname = "tailport";
- ap = agattr(root, AGEDGE, attrname, 0);
- if (!ap)
- ap = agattr(root, AGEDGE, attrname, defval);
- } else {
- ap = agattr(root, AGEDGE, name, 0);
- if (!ap)
- ap = agattr(root, AGEDGE, name, defval);
- }
- if (is_html) {
- char *val = agstrdup_html(root, value);
- agxset(ep, ap, val);
- agstrfree(root, val); // drop the extra reference count we bumped for val
- } else {
- agxset(ep, ap, value);
- }
- }
- /* setGlobalEdgeAttr:
- * Set global edge attribute.
- * The names always begin with "edge:".
- */
- static void
- setGlobalEdgeAttr(Agraph_t * g, char *name, char *value)
- {
- if (!startswith(name, EDGELBL))
- fprintf(stderr,
- "Warning: global edge attribute %s in graph %s does not begin with the prefix %s\n",
- name, agnameof(g), EDGELBL);
- else
- name += ELBLLEN;
- if ((g != root) && !agattr(root, AGEDGE, name, 0))
- agattr(root, AGEDGE, name, defval);
- agattr(g, AGEDGE, name, value);
- }
- static void
- setGraphAttr(Agraph_t * g, char *name, char *value, userdata_t * ud)
- {
- Agsym_t *ap;
- if ((g == root) && !strcmp(name, "strict") && !strcmp(value, "true")) {
- g->desc.strict = true;
- } else if (strcmp(name, "name") == 0)
- setName(ud->nameMap, (Agobj_t *) g, value);
- else {
- ap = agattr(root, AGRAPH, name, 0);
- if (ap)
- agxset(g, ap, value);
- else if (g == root)
- agattr(root, AGRAPH, name, value);
- else {
- ap = agattr(root, AGRAPH, name, defval);
- agxset(g, ap, value);
- }
- }
- }
- static void setAttr(char *name, char *value, userdata_t * ud, bool is_html)
- {
- switch (Current_class) {
- case TAG_GRAPH:
- setGraphAttr(G, name, value, ud);
- break;
- case TAG_NODE:
- setNodeAttr(N, name, value, ud, is_html);
- break;
- case TAG_EDGE:
- setEdgeAttr(E, name, value, ud, is_html);
- break;
- default:
- break;
- }
- }
- /*------------- expat handlers ----------------------------------*/
- static void
- startElementHandler(void *userData, const char *name, const char **atts)
- {
- int pos;
- userdata_t *ud = userData;
- Agraph_t *g = NULL;
- if (strcmp(name, "gxl") == 0) {
- /* do nothing */
- } else if (strcmp(name, "graph") == 0) {
- const char *edgeMode = "";
- char buf[NAMEBUF]; /* holds % + number */
- Current_class = TAG_GRAPH;
- if (ud->closedElementType == TAG_GRAPH) {
- fprintf(stderr,
- "Warning: Node contains more than one graph.\n");
- }
- pos = get_xml_attr("id", atts);
- if (pos <= 0) {
- fprintf(stderr, "Error: Graph has no ID attribute.\n");
- graphviz_exit(EXIT_FAILURE);
- }
- const char *id = atts[pos];
- pos = get_xml_attr("edgemode", atts);
- if (pos > 0) {
- edgeMode = atts[pos];
- }
- if (graph_stack_is_empty(&Gstack)) {
- if (strcmp(edgeMode, "directed") == 0) {
- g = agopen((char *) id, Agdirected, &AgDefaultDisc);
- } else if (strcmp(edgeMode, "undirected") == 0) {
- g = agopen((char *) id, Agundirected, &AgDefaultDisc);
- } else {
- fprintf(stderr,
- "Warning: graph has no edgemode attribute");
- fprintf(stderr, " - assume directed\n");
- g = agopen((char *) id, Agdirected, &AgDefaultDisc);
- }
- push_subg(g);
- } else {
- Agraph_t *subg;
- if (isAnonGraph(id)) {
- static int anon_id = 1;
- snprintf(buf, sizeof(buf), "%%%d", anon_id++);
- id = buf;
- }
- subg = agsubg(G, (char *) id, 1);
- push_subg(subg);
- }
- pos = get_xml_attr("role", atts);
- if (pos > 0) {
- setGraphAttr(G, GXL_ROLE, (char *) atts[pos], ud);
- }
- pos = get_xml_attr("hypergraph", atts);
- if (pos > 0) {
- setGraphAttr(G, GXL_HYPER, (char *) atts[pos], ud);
- }
- } else if (strcmp(name, "node") == 0) {
- Current_class = TAG_NODE;
- pos = get_xml_attr("id", atts);
- if (pos > 0) {
- const char *attrname;
- attrname = atts[pos];
- if (attrname != NULL && strcmp(attrname, "") != 0) {
- bind_node(attrname);
- }
- }
- } else if (strcmp(name, "edge") == 0) {
- const char *head = "", *tail = "";
- char *tname;
- Agnode_t *t;
- Current_class = TAG_EDGE;
- pos = get_xml_attr("from", atts);
- if (pos > 0)
- tail = atts[pos];
- pos = get_xml_attr("to", atts);
- if (pos > 0)
- head = atts[pos];
- tname = mapLookup(ud->nameMap, tail);
- if (tname)
- tail = tname;
- tname = mapLookup(ud->nameMap, head);
- if (tname)
- head = tname;
- bind_edge(tail, head);
- t = AGTAIL(E);
- tname = agnameof(t);
- if (strcmp(tname, tail) == 0) {
- ud->edgeinverted = false;
- } else if (strcmp(tname, head) == 0) {
- ud->edgeinverted = true;
- }
- pos = get_xml_attr("fromorder", atts);
- if (pos > 0) {
- setEdgeAttr(E, GXL_FROM, (char *) atts[pos], ud, false);
- }
- pos = get_xml_attr("toorder", atts);
- if (pos > 0) {
- setEdgeAttr(E, GXL_TO, (char *) atts[pos], ud, false);
- }
- pos = get_xml_attr("id", atts);
- if (pos > 0) {
- setEdgeAttr(E, GXL_ID, (char *) atts[pos], ud, false);
- }
- } else if (strcmp(name, "attr") == 0) {
- const char *attrname = atts[get_xml_attr("name", atts)];
- agxbput(&ud->xml_attr_name, attrname);
- pos = get_xml_attr("kind", atts);
- if (pos > 0) {
- if (strcmp("node", atts[pos]) == 0)
- ud->globalAttrType = TAG_NODE;
- else if (strcmp("edge", atts[pos]) == 0)
- ud->globalAttrType = TAG_EDGE;
- else if (strcmp("graph", atts[pos]) == 0)
- ud->globalAttrType = TAG_GRAPH;
- else if (strcmp("HTML-like string", atts[pos]) == 0)
- ud->globalAttrType = TAG_HTML_LIKE_STRING;
- } else {
- ud->globalAttrType = TAG_NONE;
- }
- } else if (strcmp(name, "string") == 0
- || strcmp(name, "bool") == 0
- || strcmp(name, "int") == 0 || strcmp(name, "float") == 0) {
- ud->listen = true;
- if (ud->compositeReadState) {
- agxbprint(&ud->composite_buffer, "<%s>", name);
- }
- } else if (strcmp(name, "rel") == 0 || strcmp(name, "relend") == 0) {
- fprintf(stderr, "%s element is ignored by DOT\n", name);
- } else if (strcmp(name, "type") == 0) {
- pos = get_xml_attr("xlink:href", atts);
- if (pos > 0) {
- setAttr(GXL_TYPE, (char *) atts[pos], ud, false);
- }
- } else if (strcmp(name, "locator") == 0) {
- pos = get_xml_attr("xlink:href", atts);
- if (pos > 0) {
- const char *href = atts[pos];
- agxbprint(&ud->xml_attr_value, "%s%s", GXL_LOC, href);
- }
- } else if (strcmp(name, "seq") == 0
- || strcmp(name, "set") == 0
- || strcmp(name, "bag") == 0
- || strcmp(name, "tup") == 0 || strcmp(name, "enum") == 0) {
- ud->compositeReadState = true;
- agxbprint(&ud->composite_buffer, "<%s>", name);
- } else {
- /* must be some extension */
- fprintf(stderr,
- "Unknown node %s; DOT does not support extensions.\n",
- name);
- }
- }
- static void endElementHandler(void *userData, const char *name)
- {
- userdata_t *ud = userData;
- if (strcmp(name, "graph") == 0) {
- pop_subg();
- ud->closedElementType = TAG_GRAPH;
- } else if (strcmp(name, "node") == 0) {
- Current_class = TAG_GRAPH;
- N = 0;
- ud->closedElementType = TAG_NODE;
- } else if (strcmp(name, "edge") == 0) {
- Current_class = TAG_GRAPH;
- E = 0;
- ud->closedElementType = TAG_EDGE;
- ud->edgeinverted = false;
- } else if (strcmp(name, "attr") == 0) {
- agxbuf new_name = {0};
- char *value;
- ud->closedElementType = TAG_NONE;
- if (ud->compositeReadState) {
- agxbprint(&new_name, "%s%s", GXL_COMP, agxbuse(&ud->xml_attr_name));
- value = agxbuse(&ud->composite_buffer);
- agxbclear(&ud->xml_attr_value);
- ud->compositeReadState = false;
- } else {
- agxbput(&new_name, agxbuse(&ud->xml_attr_name));
- value = agxbuse(&ud->xml_attr_value);
- }
- switch (ud->globalAttrType) {
- case TAG_NONE:
- setAttr(agxbuse(&new_name), value, ud, false);
- break;
- case TAG_NODE:
- setGlobalNodeAttr(G, agxbuse(&new_name), value);
- break;
- case TAG_EDGE:
- setGlobalEdgeAttr(G, agxbuse(&new_name), value);
- break;
- case TAG_GRAPH:
- setGraphAttr(G, agxbuse(&new_name), value, ud);
- break;
- case TAG_HTML_LIKE_STRING:
- setAttr(agxbuse(&new_name), value, ud, true);
- break;
- default:
- UNREACHABLE();
- }
- agxbfree(&new_name);
- ud->globalAttrType = TAG_NONE;
- } else if (strcmp(name, "string") == 0
- || strcmp(name, "bool") == 0
- || strcmp(name, "int") == 0 || strcmp(name, "float") == 0) {
- ud->listen = false;
- if (ud->compositeReadState) {
- agxbprint(&ud->composite_buffer, "</%s>", name);
- }
- } else if (strcmp(name, "seq") == 0
- || strcmp(name, "set") == 0
- || strcmp(name, "bag") == 0
- || strcmp(name, "tup") == 0 || strcmp(name, "enum") == 0) {
- agxbprint(&ud->composite_buffer, "</%s>", name);
- }
- }
- static void characterDataHandler(void *userData, const char *s, int length)
- {
- userdata_t *ud = userData;
- assert(length >= 0 && "Expat returned negative length data");
- size_t len = (size_t)length;
- if (!ud->listen)
- return;
- if (ud->compositeReadState) {
- agxbput_n(&ud->composite_buffer, s, len);
- return;
- }
- agxbput_n(&ud->xml_attr_value, s, len);
- }
- Agraph_t *gxl_to_gv(FILE * gxlFile)
- {
- char buf[BUFSIZ];
- int done;
- userdata_t udata = genUserdata();
- XML_Parser parser = XML_ParserCreate(NULL);
- XML_SetUserData(parser, &udata);
- XML_SetElementHandler(parser, startElementHandler, endElementHandler);
- XML_SetCharacterDataHandler(parser, characterDataHandler);
- Current_class = TAG_GRAPH;
- root = 0;
- do {
- size_t len = fread(buf, 1, sizeof(buf), gxlFile);
- if (len == 0)
- break;
- done = len < sizeof(buf);
- assert(len <= (size_t)INT_MAX && "too large data for Expat API");
- if (XML_Parse(parser, buf, (int)len, done) == XML_STATUS_ERROR) {
- fprintf(stderr,
- "%s at line %lu\n",
- XML_ErrorString(XML_GetErrorCode(parser)),
- XML_GetCurrentLineNumber(parser));
- graphviz_exit(1);
- }
- } while (!done);
- XML_ParserFree(parser);
- freeUserdata(udata);
- graph_stack_free(&Gstack);
- return root;
- }
- #endif
|