gv2gxl.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. /**
  2. * @file
  3. * @brief DOT-<a href=https://en.wikipedia.org/wiki/GXL>GXL</a> convert subroutines
  4. */
  5. /*************************************************************************
  6. * Copyright (c) 2011 AT&T Intellectual Property
  7. * All rights reserved. This program and the accompanying materials
  8. * are made available under the terms of the Eclipse Public License v1.0
  9. * which accompanies this distribution, and is available at
  10. * https://www.eclipse.org/legal/epl-v10.html
  11. *
  12. * Contributors: Details at https://graphviz.org
  13. *************************************************************************/
  14. #include <cgraph/gv_ctype.h>
  15. #include <common/types.h>
  16. #include <common/utils.h>
  17. #include "convert.h"
  18. #include <stdbool.h>
  19. #include <stdio.h>
  20. #include <stdlib.h>
  21. #include <util/agxbuf.h>
  22. #include <util/alloc.h>
  23. #include <util/startswith.h>
  24. #define EMPTY(s) ((s == 0) || (*s == '\0'))
  25. #define SLEN(s) (sizeof(s)-1)
  26. #define GXL_ATTR "_gxl_"
  27. #define GXL_ROLE "_gxl_role"
  28. #define GXL_HYPER "_gxl_hypergraph"
  29. #define GXL_ID "_gxl_id"
  30. #define GXL_FROM "_gxl_fromorder"
  31. #define GXL_TO "_gxl_toorder"
  32. #define GXL_TYPE "_gxl_type"
  33. #define GXL_COMP "_gxl_composite_"
  34. #define GXL_LOC "_gxl_locator_"
  35. #define GXL_COMP_LEN (SLEN(GXL_COMP))
  36. typedef struct {
  37. Agrec_t h;
  38. int written;
  39. } Local_Agnodeinfo_t;
  40. static int Level; /* level of tabs */
  41. static Agsym_t *Tailport, *Headport;
  42. typedef struct {
  43. Dtlink_t link;
  44. char *name;
  45. char *unique_name;
  46. } namev_t;
  47. static void *make_nitem(void *p, Dtdisc_t *disc) {
  48. (void)disc;
  49. namev_t *objp = p;
  50. namev_t *np = gv_alloc(sizeof(*np));
  51. np->name = objp->name;
  52. np->unique_name = 0;
  53. return np;
  54. }
  55. static Dtdisc_t nameDisc = {
  56. offsetof(namev_t, name),
  57. -1,
  58. offsetof(namev_t, link),
  59. make_nitem,
  60. free,
  61. NULL,
  62. };
  63. typedef struct {
  64. Dtlink_t link;
  65. char *name;
  66. } idv_t;
  67. static void free_iditem(void *idv) {
  68. idv_t *idp = idv;
  69. free(idp->name);
  70. free(idp);
  71. }
  72. static Dtdisc_t idDisc = {
  73. offsetof(idv_t, name),
  74. -1,
  75. offsetof(idv_t, link),
  76. NULL,
  77. free_iditem,
  78. NULL,
  79. };
  80. typedef struct {
  81. Dt_t *nodeMap;
  82. Dt_t *graphMap;
  83. Dt_t *synNodeMap;
  84. Dt_t *idList;
  85. Agraph_t *root;
  86. char attrsNotWritten;
  87. char directed;
  88. } gxlstate_t;
  89. static void writeBody(gxlstate_t *, Agraph_t * g, FILE * gxlFile);
  90. static void iterateBody(gxlstate_t * stp, Agraph_t * g);
  91. static void tabover(FILE * gxlFile)
  92. {
  93. int temp = Level;
  94. while (temp--)
  95. putc('\t', gxlFile);
  96. }
  97. /* legalGXLName:
  98. * By XML spec,
  99. * ID := (alpha|'_'|':')(NameChar)*
  100. * NameChar := alpha|digit|'.'|':'|'-'|'_'
  101. */
  102. static bool legalGXLName(const char *id) {
  103. char c = *id++;
  104. if (!gv_isalpha(c) && c != '_' && c != ':')
  105. return false;
  106. while ((c = *id++)) {
  107. if (!gv_isalnum(c) && c != '_' && c != ':' && c != '-' && c != '.')
  108. return false;
  109. }
  110. return true;
  111. }
  112. // `fputs` wrapper to handle the difference in calling convention to what
  113. // `xml_escape`’s `cb` expects
  114. static inline int put(void *stream, const char *s) {
  115. return fputs(s, stream);
  116. }
  117. // write a string to the given file, XML-escaping the input
  118. static inline int xml_puts(FILE *stream, const char *s) {
  119. const xml_flags_t flags = {.dash = 1, .nbsp = 1};
  120. return xml_escape(s, flags, put, stream);
  121. }
  122. // wrapper around `xml_escape` to set flags for URL escaping
  123. static int xml_url_puts(FILE *f, const char *s) {
  124. const xml_flags_t flags = {0};
  125. return xml_escape(s, flags, put, f);
  126. }
  127. static bool isGxlGrammar(const char *name) {
  128. return startswith(name, GXL_ATTR);
  129. }
  130. static bool isLocatorType(const char *name) {
  131. return startswith(name, GXL_LOC);
  132. }
  133. static bool idexists(Dt_t * ids, char *id) {
  134. return dtmatch(ids, id) != NULL;
  135. }
  136. /* addid:
  137. * assume id is not in ids.
  138. */
  139. static char *addid(Dt_t *ids, const char *id) {
  140. idv_t *idp = gv_alloc(sizeof(*idp));
  141. idp->name = gv_strdup(id);
  142. dtinsert(ids, idp);
  143. return idp->name;
  144. }
  145. static char *createGraphId(Dt_t * ids)
  146. {
  147. static int graphIdCounter = 0;
  148. agxbuf buf = {0};
  149. char *name;
  150. do {
  151. agxbprint(&buf, "G_%d", graphIdCounter++);
  152. name = agxbuse(&buf);
  153. } while (idexists(ids, name));
  154. char *rv = addid(ids, name);
  155. agxbfree(&buf);
  156. return rv;
  157. }
  158. static char *createNodeId(Dt_t * ids)
  159. {
  160. static int nodeIdCounter = 0;
  161. agxbuf buf = {0};
  162. char *name;
  163. do {
  164. agxbprint(&buf, "N_%d", nodeIdCounter++);
  165. name = agxbuse(&buf);
  166. } while (idexists(ids, name));
  167. char *rv = addid(ids, name);
  168. agxbfree(&buf);
  169. return rv;
  170. }
  171. static char *mapLookup(Dt_t * nm, char *name)
  172. {
  173. namev_t *objp = dtmatch(nm, name);
  174. if (objp)
  175. return objp->unique_name;
  176. return NULL;
  177. }
  178. /* nodeID:
  179. * Return id associated with the given node.
  180. */
  181. static char *nodeID(gxlstate_t * stp, Agnode_t * n)
  182. {
  183. char *name = agnameof(n);
  184. char *uniqueName = mapLookup(stp->nodeMap, name);
  185. assert(uniqueName);
  186. return uniqueName;
  187. }
  188. #define EDGEOP "--" /* cannot use '>'; illegal in ID in GXL */
  189. static char *createEdgeId(gxlstate_t * stp, Agedge_t * e)
  190. {
  191. int edgeIdCounter = 1;
  192. char *hname = nodeID(stp, AGHEAD(e));
  193. char *tname = nodeID(stp, AGTAIL(e));
  194. agxbuf bp = {0};
  195. agxbprint(&bp, "%s%s%s", tname, EDGEOP, hname);
  196. char *id_name = agxbuse(&bp);
  197. while (idexists(stp->idList, id_name)) {
  198. agxbprint(&bp, "%s%s%s:%d", tname, EDGEOP, hname, edgeIdCounter++);
  199. id_name = agxbuse(&bp);
  200. }
  201. char *rv = addid(stp->idList, id_name);
  202. agxbfree(&bp);
  203. return rv;
  204. }
  205. static void addToMap(Dt_t * map, char *name, char *uniqueName)
  206. {
  207. namev_t obj = {.name = name};
  208. namev_t *objp = dtinsert(map, &obj);
  209. assert(objp->unique_name == NULL);
  210. objp->unique_name = uniqueName;
  211. }
  212. static void graphAttrs(FILE * gxlFile, Agraph_t * g)
  213. {
  214. char *val = agget(g, GXL_ROLE);
  215. if (!EMPTY(val)) {
  216. fprintf(gxlFile, " role=\"");
  217. xml_puts(gxlFile, val);
  218. fprintf(gxlFile, "\"");
  219. }
  220. val = agget(g, GXL_HYPER);
  221. if (!EMPTY(val)) {
  222. fprintf(gxlFile, " hypergraph=\"");
  223. xml_puts(gxlFile, val);
  224. fprintf(gxlFile, "\"");
  225. }
  226. }
  227. static void edgeAttrs(FILE * gxlFile, Agedge_t * e)
  228. {
  229. char *val = agget(e, GXL_ID);
  230. if (!EMPTY(val)) {
  231. fprintf(gxlFile, " id=\"");
  232. xml_puts(gxlFile, val);
  233. fprintf(gxlFile, "\"");
  234. }
  235. val = agget(e, GXL_FROM);
  236. if (!EMPTY(val)) {
  237. fprintf(gxlFile, " fromorder=\"");
  238. xml_puts(gxlFile, val);
  239. fprintf(gxlFile, "\"");
  240. }
  241. val = agget(e, GXL_TO);
  242. if (!EMPTY(val)) {
  243. fprintf(gxlFile, " toorder=\"");
  244. xml_puts(gxlFile, val);
  245. fprintf(gxlFile, "\"");
  246. }
  247. }
  248. static void printHref(FILE * gxlFile, void *n)
  249. {
  250. char *val = agget(n, GXL_TYPE);
  251. if (!EMPTY(val)) {
  252. tabover(gxlFile);
  253. fprintf(gxlFile, "\t<type xlink:href=\"");
  254. xml_url_puts(gxlFile, val);
  255. fprintf(gxlFile, "\">\n");
  256. tabover(gxlFile);
  257. fprintf(gxlFile, "\t</type>\n");
  258. }
  259. }
  260. static void
  261. writeDict(FILE *gxlFile, const char *name, Dict_t *dict, bool isGraph) {
  262. Dict_t *view = dtview(dict, NULL);
  263. for (Agsym_t *sym = dtfirst(dict); sym; sym = dtnext(dict, sym)) {
  264. if (!isGxlGrammar(sym->name)) {
  265. if (EMPTY(sym->defval)) { /* try to skip empty str (default) */
  266. if (view == NULL)
  267. continue; /* no parent */
  268. Agsym_t *psym = dtsearch(view, sym);
  269. /* assert(psym); */
  270. if (EMPTY(psym->defval))
  271. continue; /* also empty in parent */
  272. }
  273. if (isLocatorType(sym->defval)) {
  274. char *locatorVal = sym->defval + strlen(GXL_LOC);
  275. tabover(gxlFile);
  276. fprintf(gxlFile, "\t<attr name=\"");
  277. xml_puts(gxlFile, sym->name);
  278. fprintf(gxlFile, "\">\n");
  279. tabover(gxlFile);
  280. fprintf(gxlFile, "\t\t<locator xlink:href=\"");
  281. xml_url_puts(gxlFile, locatorVal);
  282. fprintf(gxlFile, "\"/>\n");
  283. tabover(gxlFile);
  284. fprintf(gxlFile, "\t</attr>\n");
  285. } else {
  286. tabover(gxlFile);
  287. if (isGraph) {
  288. fprintf(gxlFile, "\t<attr name=\"");
  289. xml_puts(gxlFile, sym->name);
  290. fprintf(gxlFile, "\" ");
  291. fprintf(gxlFile, "kind=\"");
  292. xml_puts(gxlFile, name);
  293. fprintf(gxlFile, "\">\n");
  294. }
  295. else {
  296. fprintf(gxlFile, "\t<attr name=\"");
  297. xml_puts(gxlFile, name);
  298. fprintf(gxlFile, ":");
  299. xml_puts(gxlFile, sym->name);
  300. fprintf(gxlFile, "\" kind=\"");
  301. xml_puts(gxlFile, name);
  302. fprintf(gxlFile, "\">\n");
  303. }
  304. tabover(gxlFile);
  305. fprintf(gxlFile, "\t\t<string>");
  306. xml_puts(gxlFile, sym->defval);
  307. fprintf(gxlFile, "</string>\n");
  308. tabover(gxlFile);
  309. fprintf(gxlFile, "\t</attr>\n");
  310. }
  311. } else {
  312. /* gxl attr; check for special cases like composites */
  313. if (startswith(sym->name, GXL_COMP)) {
  314. if (EMPTY(sym->defval)) {
  315. if (view == NULL)
  316. continue;
  317. Agsym_t *psym = dtsearch(view, sym);
  318. if (EMPTY(psym->defval))
  319. continue;
  320. }
  321. tabover(gxlFile);
  322. fprintf(gxlFile, "\t<attr name=\"");
  323. xml_puts(gxlFile, sym->name + GXL_COMP_LEN);
  324. fprintf(gxlFile, "\" ");
  325. fprintf(gxlFile, "kind=\"");
  326. xml_puts(gxlFile, name);
  327. fprintf(gxlFile, "\">\n");
  328. tabover(gxlFile);
  329. fprintf(gxlFile, "\t\t");
  330. xml_puts(gxlFile, sym->defval);
  331. fprintf(gxlFile, "\n");
  332. tabover(gxlFile);
  333. fprintf(gxlFile, "\t</attr>\n");
  334. }
  335. }
  336. }
  337. dtview(dict, view); /* restore previous view */
  338. }
  339. static void writeDicts(Agraph_t * g, FILE * gxlFile)
  340. {
  341. Agdatadict_t *def;
  342. if ((def = agdatadict(g, false))) {
  343. writeDict(gxlFile, "graph", def->dict.g, true);
  344. writeDict(gxlFile, "node", def->dict.n, false);
  345. writeDict(gxlFile, "edge", def->dict.e, false);
  346. }
  347. }
  348. static void writeHdr(gxlstate_t *stp, Agraph_t *g, FILE *gxlFile, bool top) {
  349. char *kind;
  350. Level++;
  351. stp->attrsNotWritten = AGATTRWF(g);
  352. char *name = agnameof(g);
  353. if (g->desc.directed)
  354. kind = "directed";
  355. else
  356. kind = "undirected";
  357. if (!top && agparent(g)) {
  358. /* this must be anonymous graph */
  359. agxbuf buf = {0};
  360. agxbprint(&buf, "N_%s", name);
  361. char *bp = agxbuse(&buf);
  362. if (idexists(stp->idList, bp) || !legalGXLName(bp)) {
  363. bp = createNodeId(stp->idList);
  364. } else {
  365. bp = addid(stp->idList, bp);
  366. }
  367. addToMap(stp->synNodeMap, name, bp);
  368. tabover(gxlFile);
  369. fprintf(gxlFile, "<node id=\"%s\">\n", bp);
  370. agxbfree(&buf);
  371. Level++;
  372. } else {
  373. Tailport = agattr(g, AGEDGE, "tailport", NULL);
  374. Headport = agattr(g, AGEDGE, "headport", NULL);
  375. }
  376. char *uniqueName = mapLookup(stp->graphMap, name);
  377. tabover(gxlFile);
  378. fprintf(gxlFile, "<graph id=\"%s\" edgeids=\"true\" edgemode=\"%s\"",
  379. uniqueName, kind);
  380. graphAttrs(gxlFile, g);
  381. fprintf(gxlFile, ">\n");
  382. if (uniqueName && (strcmp(name, uniqueName) != 0)) {
  383. tabover(gxlFile);
  384. fprintf(gxlFile, "\t<attr name=\"name\">\n");
  385. tabover(gxlFile);
  386. fprintf(gxlFile, "\t\t<string>");
  387. xml_puts(gxlFile, name);
  388. fprintf(gxlFile, "</string>\n");
  389. tabover(gxlFile);
  390. fprintf(gxlFile, "\t</attr>\n");
  391. }
  392. if (agisstrict(g)) {
  393. tabover(gxlFile);
  394. fprintf(gxlFile, "\t<attr name=\"strict\">\n");
  395. tabover(gxlFile);
  396. fprintf(gxlFile, "\t\t<string>true</string>\n");
  397. tabover(gxlFile);
  398. fprintf(gxlFile, "\t</attr>\n");
  399. }
  400. writeDicts(g, gxlFile);
  401. printHref(gxlFile, g);
  402. AGATTRWF(g) = !(AGATTRWF(g));
  403. }
  404. static void writeTrl(Agraph_t *g, FILE *gxlFile, bool top) {
  405. tabover(gxlFile);
  406. fprintf(gxlFile, "</graph>\n");
  407. Level--;
  408. if (!top && agparent(g)) {
  409. tabover(gxlFile);
  410. fprintf(gxlFile, "</node>\n");
  411. Level--;
  412. }
  413. }
  414. static void writeSubgs(gxlstate_t * stp, Agraph_t * g, FILE * gxlFile)
  415. {
  416. for (Agraph_t *subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
  417. writeHdr(stp, subg, gxlFile, false);
  418. writeBody(stp, subg, gxlFile);
  419. writeTrl(subg, gxlFile, false);
  420. }
  421. }
  422. static void writeEdgeName(Agedge_t *e, FILE *gxlFile) {
  423. char *p = agnameof(e);
  424. if (!(EMPTY(p))) {
  425. tabover(gxlFile);
  426. fprintf(gxlFile, "\t<attr name=\"key\">\n");
  427. tabover(gxlFile);
  428. fprintf(gxlFile, "\t\t<string>");
  429. xml_puts(gxlFile, p);
  430. fprintf(gxlFile, "</string>\n");
  431. tabover(gxlFile);
  432. fprintf(gxlFile, "\t</attr>\n");
  433. }
  434. }
  435. static void
  436. writeNondefaultAttr(void *obj, FILE * gxlFile, Dict_t * defdict)
  437. {
  438. if (AGTYPE(obj) == AGINEDGE || AGTYPE(obj) == AGOUTEDGE) {
  439. writeEdgeName(obj, gxlFile);
  440. }
  441. Agattr_t *data = agattrrec(obj);
  442. if (data) {
  443. for (Agsym_t *sym = dtfirst(defdict); sym; sym = dtnext(defdict, sym)) {
  444. if (!isGxlGrammar(sym->name)) {
  445. if (AGTYPE(obj) == AGINEDGE || AGTYPE(obj) == AGOUTEDGE) {
  446. if (Tailport && sym->id == Tailport->id)
  447. continue;
  448. if (Headport && sym->id == Headport->id)
  449. continue;
  450. }
  451. if (data->str[sym->id] != sym->defval) {
  452. if (strcmp(data->str[sym->id], "") == 0)
  453. continue;
  454. if (isLocatorType(data->str[sym->id])) {
  455. char *locatorVal = data->str[sym->id] + strlen(GXL_LOC);
  456. tabover(gxlFile);
  457. fprintf(gxlFile, "\t<attr name=\"");
  458. xml_puts(gxlFile, sym->name);
  459. fprintf(gxlFile, "\">\n");
  460. tabover(gxlFile);
  461. fprintf(gxlFile, "\t\t<locator xlink:href=\"");
  462. xml_url_puts(gxlFile, locatorVal);
  463. fprintf(gxlFile, "\"/>\n");
  464. tabover(gxlFile);
  465. fprintf(gxlFile, "\t</attr>\n");
  466. } else {
  467. tabover(gxlFile);
  468. fprintf(gxlFile, "\t<attr name=\"");
  469. xml_puts(gxlFile, sym->name);
  470. fprintf(gxlFile, "\"");
  471. if (aghtmlstr(data->str[sym->id])) {
  472. // This is a <…> string. Note this in the kind.
  473. fprintf(gxlFile, " kind=\"HTML-like string\"");
  474. }
  475. fprintf(gxlFile, ">\n");
  476. tabover(gxlFile);
  477. fprintf(gxlFile, "\t\t<string>");
  478. xml_puts(gxlFile, data->str[sym->id]);
  479. fprintf(gxlFile, "</string>\n");
  480. tabover(gxlFile);
  481. fprintf(gxlFile, "\t</attr>\n");
  482. }
  483. }
  484. } else {
  485. /* gxl attr; check for special cases like composites */
  486. if (startswith(sym->name, GXL_COMP)) {
  487. if (data->str[sym->id] != sym->defval) {
  488. tabover(gxlFile);
  489. fprintf(gxlFile, "\t<attr name=\"");
  490. xml_puts(gxlFile, sym->name + GXL_COMP_LEN);
  491. fprintf(gxlFile, "\">\n");
  492. tabover(gxlFile);
  493. fprintf(gxlFile, "\t\t");
  494. xml_puts(gxlFile, data->str[sym->id]);
  495. fprintf(gxlFile, "\n");
  496. tabover(gxlFile);
  497. fprintf(gxlFile, "\t</attr>\n");
  498. }
  499. }
  500. }
  501. }
  502. }
  503. AGATTRWF(obj) = !(AGATTRWF(obj));
  504. }
  505. static bool attrs_written(gxlstate_t * stp, void *obj) {
  506. return AGATTRWF(obj) != stp->attrsNotWritten;
  507. }
  508. static void
  509. writeNode(gxlstate_t * stp, Agnode_t * n, FILE * gxlFile, Dict_t * d)
  510. {
  511. char *name = agnameof(n);
  512. char *uniqueName = nodeID(stp, n);
  513. Level++;
  514. tabover(gxlFile);
  515. fprintf(gxlFile, "<node id=\"%s\">\n", uniqueName);
  516. printHref(gxlFile, n);
  517. if (strcmp(name, uniqueName)) {
  518. tabover(gxlFile);
  519. fprintf(gxlFile, "\t<attr name=\"name\">\n");
  520. tabover(gxlFile);
  521. fprintf(gxlFile, "\t\t<string>");
  522. xml_puts(gxlFile, name);
  523. fprintf(gxlFile, "</string>\n");
  524. tabover(gxlFile);
  525. fprintf(gxlFile, "\t</attr>\n");
  526. }
  527. if (!attrs_written(stp, n))
  528. writeNondefaultAttr(n, gxlFile, d);
  529. tabover(gxlFile);
  530. fprintf(gxlFile, "</node>\n");
  531. Level--;
  532. }
  533. static void writePort(Agedge_t * e, FILE * gxlFile, char *name)
  534. {
  535. char *val = agget(e, name);
  536. if (val && val[0]) {
  537. tabover(gxlFile);
  538. fprintf(gxlFile, "\t<attr name=\"");
  539. xml_puts(gxlFile, name);
  540. fprintf(gxlFile, "\">\n");
  541. tabover(gxlFile);
  542. fprintf(gxlFile, "\t\t<string>");
  543. xml_puts(gxlFile, val);
  544. fprintf(gxlFile, "</string>\n");
  545. tabover(gxlFile);
  546. fprintf(gxlFile, "\t</attr>\n");
  547. }
  548. }
  549. static bool writeEdgeTest(Agraph_t *g, Agedge_t *e) {
  550. /* can use agedge() because we subverted the dict compar_f */
  551. for (Agraph_t *subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
  552. if (agsubedge(subg, e, 0))
  553. return false;
  554. }
  555. return true;
  556. }
  557. static void
  558. writeEdge(gxlstate_t * stp, Agedge_t * e, FILE * gxlFile, Dict_t * d)
  559. {
  560. Agnode_t *t = AGTAIL(e);
  561. Agnode_t *h = AGHEAD(e);
  562. Level++;
  563. tabover(gxlFile);
  564. fprintf(gxlFile, "<edge from=\"%s\" ", nodeID(stp, t));
  565. fprintf(gxlFile, "to=\"%s\"", nodeID(stp, h));
  566. edgeAttrs(gxlFile, e);
  567. if (stp->directed) {
  568. fprintf(gxlFile, " isdirected=\"true\"");
  569. } else {
  570. fprintf(gxlFile, " isdirected=\"false\"");
  571. }
  572. char *edge_id = agget(e, GXL_ID);
  573. if (!EMPTY(edge_id)) {
  574. fprintf(gxlFile, ">\n");
  575. } else {
  576. char *bp = createEdgeId(stp, e);
  577. fprintf(gxlFile, " id=\"%s\">\n", bp);
  578. }
  579. printHref(gxlFile, e);
  580. writePort(e, gxlFile, "tailport");
  581. writePort(e, gxlFile, "headport");
  582. if (!(attrs_written(stp, e)))
  583. writeNondefaultAttr(e, gxlFile, d);
  584. else
  585. writeEdgeName(e, gxlFile);
  586. tabover(gxlFile);
  587. fprintf(gxlFile, "</edge>\n");
  588. Level--;
  589. }
  590. #define writeval(n) (((Local_Agnodeinfo_t*)((n)->base.data))->written)
  591. static void writeBody(gxlstate_t * stp, Agraph_t * g, FILE * gxlFile)
  592. {
  593. writeSubgs(stp, g, gxlFile);
  594. Agdatadict_t *dd = agdatadict(g, false);
  595. for (Agnode_t *n = agfstnode(g); n; n = agnxtnode(g, n)) {
  596. Agnode_t *realn = agidnode(stp->root, AGID(n), 0);
  597. if (!writeval(realn)) {
  598. writeval(realn) = 1;
  599. writeNode(stp, n, gxlFile, dd->dict.n);
  600. }
  601. for (Agedge_t *e = agfstout(g, n); e; e = agnxtout(g, e)) {
  602. if (writeEdgeTest(g, e))
  603. writeEdge(stp, e, gxlFile, dd->dict.e);
  604. }
  605. }
  606. }
  607. static void iterateHdr(gxlstate_t * stp, Agraph_t * g)
  608. {
  609. char *name = agnameof(g);
  610. char *gxlId = agget(g, GXL_ID);
  611. if (EMPTY(gxlId))
  612. gxlId = name;
  613. if (idexists(stp->idList, gxlId) || !legalGXLName(gxlId))
  614. gxlId = createGraphId(stp->idList);
  615. else
  616. gxlId = addid(stp->idList, gxlId);
  617. addToMap(stp->graphMap, name, gxlId);
  618. }
  619. static void iterate_subgs(gxlstate_t * stp, Agraph_t * g)
  620. {
  621. for (Agraph_t *subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
  622. iterateHdr(stp, subg);
  623. iterateBody(stp, subg);
  624. }
  625. }
  626. static void iterateBody(gxlstate_t * stp, Agraph_t * g)
  627. {
  628. iterate_subgs(stp, g);
  629. for (Agnode_t *n = agfstnode(g); n; n = agnxtnode(g, n)) {
  630. char *nodename = agnameof(n);
  631. if (!mapLookup(stp->nodeMap, nodename)) {
  632. char *gxlId = agget(n, GXL_ID);
  633. if (EMPTY(gxlId))
  634. gxlId = nodename;
  635. if (idexists(stp->idList, gxlId) || !legalGXLName(gxlId))
  636. gxlId = createNodeId(stp->idList);
  637. else
  638. gxlId = addid(stp->idList, gxlId);
  639. addToMap(stp->nodeMap, nodename, gxlId);
  640. }
  641. for (Agedge_t *e = agfstout(g, n); e; e = agnxtout(g, e)) {
  642. if (writeEdgeTest(g, e)) {
  643. char *edge_id = agget(e, GXL_ID);
  644. if (!EMPTY(edge_id))
  645. addid(stp->idList, edge_id);
  646. }
  647. }
  648. }
  649. }
  650. static gxlstate_t initState(Agraph_t *g) {
  651. gxlstate_t stp = {0};
  652. stp.nodeMap = dtopen(&nameDisc, Dtoset);
  653. stp.graphMap = dtopen(&nameDisc, Dtoset);
  654. stp.synNodeMap = dtopen(&nameDisc, Dtoset);
  655. stp.idList = dtopen(&idDisc, Dtoset);
  656. stp.attrsNotWritten = 0;
  657. stp.root = g;
  658. stp.directed = agisdirected(g) != 0;
  659. return stp;
  660. }
  661. static void freeState(gxlstate_t stp) {
  662. dtclose(stp.nodeMap);
  663. dtclose(stp.graphMap);
  664. dtclose(stp.synNodeMap);
  665. dtclose(stp.idList);
  666. }
  667. void gv_to_gxl(Agraph_t * g, FILE * gxlFile)
  668. {
  669. gxlstate_t stp = initState(g);
  670. aginit(g, AGNODE, "node", sizeof(Local_Agnodeinfo_t), true);
  671. iterateHdr(&stp, g);
  672. iterateBody(&stp, g);
  673. Level = 0;
  674. fprintf(gxlFile, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n");
  675. fprintf(gxlFile, "<gxl>\n");
  676. writeHdr(&stp, g, gxlFile, true);
  677. writeBody(&stp, g, gxlFile);
  678. writeTrl(g, gxlFile, true);
  679. fprintf(gxlFile, "</gxl>\n");
  680. freeState(stp);
  681. }