Graphviz 16.1.1~dev.20261017.1519
Loading...
Searching...
No Matches
write.c
Go to the documentation of this file.
1
9/*************************************************************************
10 * Copyright (c) 2011 AT&T Intellectual Property
11 * All rights reserved. This program and the accompanying materials
12 * are made available under the terms of the Eclipse Public License v2.0
13 * which accompanies this distribution, and is available at
14 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
15 *
16 * Contributors: Details at https://graphviz.org
17 *************************************************************************/
18
19#include "config.h"
20
21#include <assert.h>
22#include <cdt/cdt.h>
23#include <cgraph/agstrcanon.h>
24#include <cgraph/cghdr.h>
25#include <ctype.h>
26#include <inttypes.h>
27#include <limits.h>
28#include <stdbool.h>
29#include <stddef.h>
30#include <stdio.h> /* need sprintf() */
31#include <stdlib.h>
32#include <util/alloc.h>
33#include <util/bitarray.h>
34#include <util/gv_ctype.h>
35#include <util/strcasecmp.h>
36#include <util/unused.h>
37
38#define EMPTY(s) (((s) == 0) || (s)[0] == '\0')
39
40typedef void iochan_t;
41
42static int ioput(Agraph_t *g, iochan_t *ofile, char *str) {
43 return AGDISC(g, io)->putstr(ofile, str);
44}
45
46#define MAX_OUTPUTLINE 128
47#define MIN_OUTPUTLINE 60
50
52typedef struct {
54 unsigned seq;
57
58typedef struct {
59 uint64_t *preorder_number; // of a graph or subgraph
60 uint64_t
61 *node_last_written; // postorder number of subg when node was last written
64 int level; // indentation level
65} info_t;
66
67static int write_body(Agraph_t *g, iochan_t *ofile, info_t *info);
68
70static void after_write(info_t);
71
72static int indent(Agraph_t *g, iochan_t *ofile, const info_t info) {
73 for (int i = info.level; i > 0; i--) {
74 if (ioput(g, ofile, "\t") == EOF) {
75 return EOF;
76 }
77 }
78 return 0;
79}
80
81// alphanumeric, '.', '-', or non-ascii; basically, chars used in unquoted ids
82static bool is_id_char(char c) {
83 return gv_isalnum(c) || c == '.' || c == '-' || !isascii(c);
84}
85
86// is the prefix of this string a recognized Graphviz escape sequence?
87// https://graphviz.org/docs/attr-types/escString/
88static bool is_escape(const char *str) {
89 assert(str != NULL);
90
91 if (*str != '\\')
92 return false;
93
94 if (str[1] == 'E')
95 return true;
96 if (str[1] == 'G')
97 return true;
98 if (str[1] == 'H')
99 return true;
100 if (str[1] == 'L')
101 return true;
102 if (str[1] == 'N')
103 return true;
104 if (str[1] == 'T')
105 return true;
106
107 if (str[1] == 'l')
108 return true;
109 if (str[1] == 'n')
110 return true;
111 if (str[1] == 'r')
112 return true;
113
114 if (str[1] == '\\')
115 return true;
116
117 if (str[1] == '"')
118 return true;
119
120 return false;
121}
122
123/* Canonicalize ordinary strings.
124 * Assumes buf is large enough to hold output.
125 */
126static char *agstrcanon_(char *arg, char *buf) {
127 char *s, *p;
128 char uc;
129 int cnt = 0, dotcnt = 0;
130 bool needs_quotes = false;
131 bool part_of_escape = false;
132 bool maybe_num;
133 bool backslash_pending = false;
134 static const char *tokenlist[] /* must agree with scan.l */
135 = {"node", "edge", "strict", "graph", "digraph", "subgraph", NULL};
136 const char **tok;
137
138 if (EMPTY(arg))
139 return "\"\"";
140 s = arg;
141 p = buf;
142 *p++ = '\"';
143 uc = *s++;
144 maybe_num = gv_isdigit(uc) || uc == '.' || uc == '-';
145 while (uc) {
146 if (uc == '\"' && !part_of_escape) {
147 *p++ = '\\';
148 needs_quotes = true;
149 } else if (!part_of_escape && is_escape(&s[-1])) {
150 needs_quotes = true;
151 part_of_escape = true;
152 } else if (maybe_num) {
153 if (uc == '-') {
154 if (cnt) {
155 maybe_num = false;
156 needs_quotes = true;
157 }
158 } else if (uc == '.') {
159 if (dotcnt++) {
160 maybe_num = false;
161 needs_quotes = true;
162 }
163 } else if (!gv_isdigit(uc)) {
164 maybe_num = false;
165 needs_quotes = true;
166 }
167 part_of_escape = false;
168 } else if (!(gv_isalnum(uc) || uc == '_' || !isascii(uc))) {
169 needs_quotes = true;
170 part_of_escape = false;
171 } else {
172 part_of_escape = false;
173 }
174 *p++ = uc;
175 uc = *s++;
176 cnt++;
177
178 /* If breaking long strings into multiple lines, only allow breaks after a
179 * non-id char, not a backslash, where the next char is an id char.
180 */
181 if (Max_outputline) {
182 if (uc && backslash_pending && !(is_id_char(p[-1]) || p[-1] == '\\') &&
183 is_id_char(uc)) {
184 *p++ = '\\';
185 *p++ = '\n';
186 needs_quotes = true;
187 backslash_pending = false;
188 cnt = 0;
189 } else if (uc && (cnt >= Max_outputline)) {
190 if (!(is_id_char(p[-1]) || p[-1] == '\\') && is_id_char(uc)) {
191 *p++ = '\\';
192 *p++ = '\n';
193 needs_quotes = true;
194 cnt = 0;
195 } else {
196 backslash_pending = true;
197 }
198 }
199 }
200 }
201 *p++ = '\"';
202 *p = '\0';
203 if (needs_quotes || (cnt == 1 && (*arg == '.' || *arg == '-')))
204 return buf;
205
206 /* Use quotes to protect tokens (example, a node named "node") */
207 /* It would be great if it were easier to use flex here. */
208 for (tok = tokenlist; *tok; tok++)
209 if (!strcasecmp(*tok, arg))
210 return buf;
211 return arg;
212}
213
217static char *agcanonhtmlstr(const char *arg, char *buf) {
218 sprintf(buf, "<%s>", arg);
219 return buf;
220}
221
226char *agstrcanon(char *arg, char *buf) {
227 if (aghtmlstr(arg))
228 return agcanonhtmlstr(arg, buf);
229 else
230 return agstrcanon_(arg, buf);
231}
232
233static int write_canonstr_(Agraph_t *g, iochan_t *ofile, char *str, bool chk) {
234
235 // maximum bytes required for canonicalized string
236 const size_t required = agstrcanon_bytes(str);
237
238 // allocate space to stage the canonicalized string
239 char *const scratch = malloc(required);
240 if (scratch == NULL) {
241 return EOF;
242 }
243
244 char *const canonicalized =
245 chk ? agstrcanon(str, scratch) : agstrcanon_(str, scratch);
246 const int rc = ioput(g, ofile, canonicalized);
247 free(scratch);
248 return rc;
249}
250
252static int write_canonstr(Agraph_t *g, iochan_t *ofile, char *str, bool known) {
253 char *s;
254
255 /* str may not have been allocated by agstrdup, so we first need to turn it
256 * into a valid refstr
257 */
258 s = known ? str : agstrdup(g, str);
259
260 int r = write_canonstr_(g, ofile, s, true);
261
262 if (!known) {
263 agstrfree(g, s, false);
264 }
265 return r;
266}
267
268static int write_dict(Agraph_t *g, iochan_t *ofile, char *name, Dict_t *dict,
269 bool top, info_t *info) {
270 int cnt = 0;
271 Agsym_t *sym, *psym;
272
273 Dict_t *const view = !top ? dtview(dict, NULL) : NULL;
274 for (sym = dtfirst(dict); sym; sym = dtnext(dict, sym)) {
275 if (EMPTY(sym->defval) &&
276 !sym->print) { /* try to skip empty str (default) */
277 if (view == NULL)
278 continue; /* no parent */
279 psym = dtsearch(view, sym);
280 assert(psym);
281 if (EMPTY(psym->defval) && psym->print)
282 continue; /* also empty in parent */
283 }
284 if (cnt++ == 0) {
285 if (indent(g, ofile, *info) == EOF || ioput(g, ofile, name) == EOF ||
286 ioput(g, ofile, " [") == EOF) {
287 return EOF;
288 }
289 info->level++;
290 } else {
291 if (ioput(g, ofile, ",\n") == EOF || indent(g, ofile, *info) == EOF) {
292 return EOF;
293 }
294 }
295 if (write_canonstr(g, ofile, sym->name, true) == EOF ||
296 ioput(g, ofile, "=") == EOF ||
297 write_canonstr(g, ofile, sym->defval, true) == EOF) {
298 return EOF;
299 }
300 }
301 if (cnt > 0) {
302 info->level--;
303 if (cnt > 1) {
304 if (ioput(g, ofile, "\n") == EOF || indent(g, ofile, *info) == EOF) {
305 return EOF;
306 }
307 }
308 if (ioput(g, ofile, "];\n") == EOF) {
309 return EOF;
310 }
311 }
312 if (!top)
313 dtview(dict, view); /* restore previous view */
314 return 0;
315}
316
317static int write_dicts(Agraph_t *g, iochan_t *ofile, bool top, info_t *info) {
318 Agdatadict_t *def;
319 if ((def = agdatadict(g, false))) {
320 if (write_dict(g, ofile, "graph", def->dict.g, top, info) == EOF ||
321 write_dict(g, ofile, "node", def->dict.n, top, info) == EOF ||
322 write_dict(g, ofile, "edge", def->dict.e, top, info) == EOF) {
323 return EOF;
324 }
325 }
326 return 0;
327}
328
329static int write_hdr(Agraph_t *g, iochan_t *ofile, bool top, info_t *info) {
330 char *name, *sep, *kind, *strict;
331 bool root = false;
332 bool hasName = true;
333
334 strict = "";
335 if (!top && agparent(g))
336 kind = "sub";
337 else {
338 root = true;
339 if (g->desc.directed)
340 kind = "di";
341 else
342 kind = "";
343 if (agisstrict(g))
344 strict = "strict ";
347 }
348 name = agnameof(g);
349 sep = " ";
350 if (!name || name[0] == LOCALNAMEPREFIX) {
351 sep = name = "";
352 hasName = false;
353 }
354 if (indent(g, ofile, *info) == EOF || ioput(g, ofile, strict) == EOF) {
355 return EOF;
356 }
357
358 /* output "<kind>graph" only for root graphs or graphs with names */
359 if (root || hasName) {
360 if (ioput(g, ofile, kind) == EOF || ioput(g, ofile, "graph ") == EOF) {
361 return EOF;
362 }
363 }
364 if (hasName) {
365 if (write_canonstr(g, ofile, name, false) == EOF) {
366 return EOF;
367 }
368 }
369 if (ioput(g, ofile, sep) == EOF || ioput(g, ofile, "{\n") == EOF) {
370 return EOF;
371 }
372 info->level++;
373 if (write_dicts(g, ofile, top, info) == EOF) {
374 return EOF;
375 }
376 AGATTRWF(g) = true;
377 return 0;
378}
379
380static int write_trl(Agraph_t *g, iochan_t *ofile, info_t *info) {
381 info->level--;
382 if (indent(g, ofile, *info) == EOF || ioput(g, ofile, "}\n") == EOF) {
383 return EOF;
384 }
385 return 0;
386}
387
392static bool is_anonymous(Agraph_t *g) {
393 assert(g != NULL);
394
395 // handle the common case inline for performance
396 if (AGDISC(g, id) == &AgIdDisc) {
397 // replicate `idprint`
398 const IDTYPE id = AGID(g);
399 if (id % 2 != 0) {
400 return true;
401 }
402 return *(char *)(uintptr_t)id == LOCALNAMEPREFIX;
403 }
404
405 const char *const name = agnameof(g);
406 return name == NULL || name[0] == LOCALNAMEPREFIX;
407}
408
410 int i, n;
411 Agattr_t *sdata, *pdata, *rdata;
412 Agdatadict_t *dd;
413
414 if (!is_anonymous(g))
415 return false;
416 if ((sdata = agattrrec(g)) && (pdata = agattrrec(agparent(g)))) {
417 rdata = agattrrec(agroot(g));
418 n = dtsize(rdata->dict);
419 for (i = 0; i < n; i++)
420 if (sdata->str[i] && pdata->str[i] &&
421 strcmp(sdata->str[i], pdata->str[i]))
422 return false;
423 }
424 dd = agdatadict(g, false);
425 if (!dd)
426 return true;
427 if (dtsize(dd->dict.n) > 0 || dtsize(dd->dict.e) > 0)
428 return false;
429 return true;
430}
431
432static bool has_no_edges(Agraph_t *g, Agnode_t *n) {
433 return agfstin(g, n) == NULL && agfstout(g, n) == NULL;
434}
435
437 Agattr_t *data;
438 Agsym_t *sym;
439
440 (void)g;
441 if ((data = agattrrec(n))) {
442 for (sym = dtfirst(data->dict); sym; sym = dtnext(data->dict, sym)) {
443 if (data->str[sym->id] != sym->defval)
444 return true;
445 }
446 }
447 return false;
448}
449
450static int write_subgs(Agraph_t *g, iochan_t *ofile, info_t *info) {
451 Agraph_t *subg;
452
453 for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
454 if (irrelevant_subgraph(subg)) {
455 write_subgs(subg, ofile, info);
456 } else {
457 if (write_hdr(subg, ofile, false, info) == EOF ||
458 write_body(subg, ofile, info) == EOF ||
459 write_trl(subg, ofile, info) == EOF) {
460 return EOF;
461 }
462 }
463 }
464 return 0;
465}
466
467static int write_edge_name(Agedge_t *e, iochan_t *ofile, bool terminate,
468 info_t *info) {
469 char *p;
470 Agraph_t *g;
471
472 p = agnameof(e);
473 g = agraphof(e);
474 if (!EMPTY(p)) {
475 if (!terminate) {
476 info->level++;
477 }
478 if (ioput(g, ofile, "\t[key=") == EOF ||
479 write_canonstr(g, ofile, p, false) == EOF) {
480 return EOF;
481 }
482 if (terminate) {
483 if (ioput(g, ofile, "]") == EOF) {
484 return EOF;
485 }
486 }
487 return 1;
488 }
489 return 0;
490}
491
492static int write_nondefault_attrs(void *obj, iochan_t *ofile, Dict_t *defdict,
493 info_t *info) {
494 Agattr_t *data;
495 Agsym_t *sym;
496 Agraph_t *g;
497 int cnt = 0;
498
499 if (AGTYPE(obj) == AGINEDGE || AGTYPE(obj) == AGOUTEDGE) {
500 const int rv = write_edge_name(obj, ofile, false, info);
501 if (rv == EOF) {
502 return EOF;
503 }
504 if (rv)
505 cnt++;
506 }
507 data = agattrrec(obj);
508 g = agraphof(obj);
509 if (data)
510 for (sym = dtfirst(defdict); sym; sym = dtnext(defdict, sym)) {
511 if (AGTYPE(obj) == AGINEDGE || AGTYPE(obj) == AGOUTEDGE) {
512 if (Tailport && sym->id == Tailport->id)
513 continue;
514 if (Headport && sym->id == Headport->id)
515 continue;
516 }
517 if (data->str[sym->id] != sym->defval) {
518 if (cnt++ == 0) {
519 if (ioput(g, ofile, "\t[") == EOF) {
520 return EOF;
521 }
522 info->level++;
523 } else {
524 if (ioput(g, ofile, ",\n") == EOF || indent(g, ofile, *info) == EOF) {
525 return EOF;
526 }
527 }
528 if (write_canonstr(g, ofile, sym->name, true) == EOF ||
529 ioput(g, ofile, "=") == EOF ||
530 write_canonstr(g, ofile, data->str[sym->id], true) == EOF) {
531 return EOF;
532 }
533 }
534 }
535 if (cnt > 0) {
536 if (ioput(g, ofile, "]") == EOF) {
537 return EOF;
538 }
539 info->level--;
540 }
541 AGATTRWF(obj) = true;
542 return 0;
543}
544
545static int write_nodename(Agnode_t *n, iochan_t *ofile) {
546 char *name;
547 Agraph_t *g;
548
549 name = agnameof(n);
550 g = agraphof(n);
551 if (name) {
552 if (write_canonstr(g, ofile, name, false) == EOF) {
553 return EOF;
554 }
555 } else {
556 char buf[sizeof("__SUSPECT") + 20];
557 snprintf(buf, sizeof(buf), "_%" PRIu64 "_SUSPECT",
558 AGID(n)); /* could be deadly wrong */
559 if (ioput(g, ofile, buf) == EOF) {
560 return EOF;
561 }
562 }
563 return 0;
564}
565
566static int attrs_written(void *obj) { return AGATTRWF(obj); }
567
568static int write_node(Agraph_t *subg, Agnode_t *n, iochan_t *ofile, Dict_t *d,
569 info_t *info) {
570 Agraph_t *g;
571
572 g = agraphof(n);
573 if (indent(g, ofile, *info) == EOF || write_nodename(n, ofile) == EOF) {
574 return EOF;
575 }
576 if (!attrs_written(n)) {
577 if (write_nondefault_attrs(n, ofile, d, info) == EOF) {
578 return EOF;
579 }
580 }
581 info->node_last_written[AGSEQ(n)] = info->preorder_number[AGSEQ(subg)];
582 return ioput(g, ofile, ";\n");
583}
584
585/* node must be written if it wasn't already emitted because of
586 * a subgraph or one of its predecessors, and if it is a singleton
587 * or has non-default attributes.
588 */
590 /* test if node was already written in g or a subgraph of g */
591 if (info->node_last_written[AGSEQ(n)] >= info->preorder_number[AGSEQ(g)])
592 return false;
593
594 if (has_no_edges(g, n) || not_default_attrs(g, n))
595 return true;
596 return false;
597}
598
599static int write_port(Agedge_t *e, iochan_t *ofile, Agsym_t *port) {
600 char *val;
601 Agraph_t *g;
602
603 if (!port)
604 return 0;
605 g = agraphof(e);
606 val = agxget(e, port);
607 if (val[0] == '\0')
608 return 0;
609
610 if (ioput(g, ofile, ":") == EOF) {
611 return EOF;
612 }
613 if (aghtmlstr(val)) {
614 if (write_canonstr(g, ofile, val, true) == EOF) {
615 return EOF;
616 }
617 } else {
618 char *s = strchr(val, ':');
619 if (s) {
620 *s = '\0';
621 if (write_canonstr_(g, ofile, val, false) == EOF ||
622 ioput(g, ofile, ":") == EOF ||
623 write_canonstr_(g, ofile, s + 1, false) == EOF) {
624 return EOF;
625 }
626 *s = ':';
627 } else {
628 if (write_canonstr_(g, ofile, val, false) == EOF) {
629 return EOF;
630 }
631 }
632 }
633 return 0;
634}
635
636static int write_edge(Agedge_t *e, iochan_t *ofile, Dict_t *d, info_t *info) {
637 Agnode_t *t, *h;
638 Agraph_t *g;
639
640 t = AGTAIL(e);
641 h = AGHEAD(e);
642 g = agraphof(t);
643 if (indent(g, ofile, *info) == EOF || write_nodename(t, ofile) == EOF ||
644 write_port(e, ofile, Tailport) == EOF ||
645 ioput(g, ofile, (agisdirected(agraphof(t)) ? " -> " : " -- ")) == EOF ||
646 write_nodename(h, ofile) == EOF ||
647 write_port(e, ofile, Headport) == EOF) {
648 return EOF;
649 }
650 if (!attrs_written(e)) {
651 if (write_nondefault_attrs(e, ofile, d, info) == EOF) {
652 return EOF;
653 }
654 } else {
655 if (write_edge_name(e, ofile, true, info) == EOF) {
656 return EOF;
657 }
658 }
659 return ioput(g, ofile, ";\n");
660}
661
668static int write_edges(iochan_t *ofile, Dict_t *d, info_t *info) {
669 for (pending_edge_t *e = dtfirst(info->edges_todo); e != NULL;
670 e = dtnext(info->edges_todo, e)) {
671 if (write_edge(e->edge, ofile, d, info) == EOF) {
672 return EOF;
673 }
674 bitarray_set(&info->edges_done, e->seq, true);
675 }
676 dtclear(info->edges_todo);
677 return 0;
678}
679
680static int write_body(Agraph_t *g, iochan_t *ofile, info_t *info) {
681 Agnode_t *n, *prev;
682 Agedge_t *e;
683 Agdatadict_t *dd;
684
685 if (write_subgs(g, ofile, info) == EOF) {
686 return EOF;
687 }
688 dd = agdatadict(g, false);
689 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
690 if (write_node_test(g, n, info)) {
691 if (write_node(g, n, ofile, dd ? dd->dict.n : 0, info) == EOF) {
692 return EOF;
693 }
694 }
695 prev = n;
696 for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
697 if (prev != aghead(e) && write_node_test(g, aghead(e), info)) {
698 if (write_node(g, aghead(e), ofile, dd ? dd->dict.n : 0, info) == EOF) {
699 return EOF;
700 }
701 prev = aghead(e);
702 }
703 // pend this edge to be emitted later
704 pending_edge_t pe = {.seq = AGSEQ(e), .edge = e};
705 if (!bitarray_get(info->edges_done, pe.seq)) {
706 (void)dtinsert(info->edges_todo, &pe);
707 }
708 }
709 }
710 // flush pending edges to the output file
711 if (write_edges(ofile, dd ? dd->dict.e : NULL, info) == EOF) {
712 return EOF;
713 }
714 return 0;
715}
716
717static void set_attrwf(Agraph_t *g, bool toplevel, bool value) {
718 Agraph_t *subg;
719 Agnode_t *n;
720 Agedge_t *e;
721
722 AGATTRWF(g) = value;
723 for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
724 set_attrwf(subg, false, value);
725 }
726 if (toplevel) {
727 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
728 AGATTRWF(n) = value;
729 for (e = agfstout(g, n); e; e = agnxtout(g, e))
730 AGATTRWF(e) = value;
731 }
732 }
733}
734
736int agwrite(Agraph_t *g, void *ofile) {
737 char *s;
738 s = agget(g, "linelength");
739 if (s != NULL && gv_isdigit(*s)) {
740 unsigned long len = strtoul(s, NULL, 10);
741 if ((len == 0 || len >= MIN_OUTPUTLINE) && len <= INT_MAX)
742 Max_outputline = (int)len;
743 }
745 if (write_hdr(g, ofile, true, &info) == EOF) {
747 return EOF;
748 }
749 if (write_body(g, ofile, &info) == EOF) {
751 return EOF;
752 }
753 if (write_trl(g, ofile, &info) == EOF) {
755 return EOF;
756 }
759 return AGDISC(g, io)->flush(ofile);
760}
761
762static uint64_t subgdfs(Agraph_t *g, uint64_t ix, info_t *info) {
763 uint64_t ix0 = ix;
764 Agraph_t *subg;
765
766 info->preorder_number[AGSEQ(g)] = ix0;
767 for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
768 ix0 = subgdfs(subg, ix0, info);
769 }
770 return ix0 + 1;
771}
772
773static void *copy(void *p, Dtdisc_t *disc UNUSED) {
774 const pending_edge_t *const src = p;
775 pending_edge_t *const dst = gv_alloc(sizeof(*dst));
776 dst->seq = src->seq;
777 dst->edge = src->edge;
778 return dst;
779}
780
781static int cmp(void *a, void *b) {
782 const unsigned *const a_seq = a;
783 const unsigned *const b_seq = b;
784 if (*a_seq < *b_seq) {
785 return -1;
786 }
787 if (*a_seq > *b_seq) {
788 return 1;
789 }
790 return 0;
791}
792
795 .key = offsetof(pending_edge_t, seq), // key offset
796 .size = sizeof(unsigned), // size of key
797 .link = offsetof(pending_edge_t, link), // Dtlink_t field offset
798 .makef = copy, // copy constructor for a `pending_edge_t`
799 .freef = free, // destructor for a `pending_edge_t`
800 .comparf = cmp, // comparator for `pending_edge_t` values
801};
802
804 info_t info = {0};
805 set_attrwf(g, true, false);
806
807 info.preorder_number = gv_calloc(g->clos->seq[AGRAPH] + 1, sizeof(uint64_t));
808 info.node_last_written =
809 gv_calloc(g->clos->seq[AGNODE] + 1, sizeof(uint64_t));
810 info.edges_todo = dtopen(&edge_disc, Dtoset);
811 info.edges_done = bitarray_new(g->clos->seq[AGEDGE] + 1);
812 subgdfs(g, 1, &info);
813 return info;
814}
815
816static void after_write(info_t info) {
817 free(info.preorder_number);
818 free(info.node_last_written);
819 bitarray_reset(&info.edges_done);
820 dtclose(info.edges_todo);
821}
Helpers for dealing with agstrcanon
static size_t agstrcanon_bytes(const char *str)
how many bytes are needed to canonicalize the given string
Definition agstrcanon.h:11
Memory allocation wrappers that exit on failure.
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
static void * gv_alloc(size_t size)
Definition alloc.h:47
API for compacted arrays of booleans.
static bitarray_t bitarray_new(size_t size_bits)
create an array of the given element length
Definition bitarray.h:47
static bool bitarray_get(bitarray_t self, size_t index)
get the value of the given element
Definition bitarray.h:65
static void bitarray_set(bitarray_t *self, size_t index, bool value)
set or clear the value of the given element
Definition bitarray.h:80
static void bitarray_reset(bitarray_t *self)
free underlying resources and leave a bit array empty
Definition bitarray.h:114
container data types API
#define dtclear(d)
Definition cdt.h:189
#define dtsearch(d, o)
Definition cdt.h:184
CDT_API int dtsize(Dt_t *)
Definition dtsize.c:14
#define dtinsert(d, o)
Definition cdt.h:186
CDT_API Dt_t * dtview(Dt_t *, Dt_t *)
Definition dtview.c:92
CDT_API int dtclose(Dt_t *)
Definition dtclose.c:10
CDT_API Dtmethod_t * Dtoset
ordered set (self-adjusting tree)
Definition dttree.c:306
CDT_API Dt_t * dtopen(Dtdisc_t *, Dtmethod_t *)
Definition dtopen.c:11
#define dtnext(d, o)
Definition cdt.h:181
#define dtfirst(d)
Definition cdt.h:180
cgraph.h additions
#define AGDISC(g, d)
Definition cghdr.h:48
#define LOCALNAMEPREFIX
Definition cghdr.h:46
static Dtdisc_t disc
Definition exparse.y:209
static double len(glCompPoint p)
Definition glutils.c:138
void * malloc(YYSIZE_T)
void free(void *)
node NULL
Definition grammar.y:181
static int cnt(Dict_t *d, Dtlink_t **set)
Definition graph.c:204
Agattr_t * agattrrec(void *obj)
Definition attr.c:205
Agsym_t * agattr_text(Agraph_t *g, int kind, char *name, const char *value)
creates or looks up text attributes of a graph
Definition attr.c:333
char * agget(void *obj, char *name)
Definition attr.c:447
Agdatadict_t * agdatadict(Agraph_t *g, bool cflag)
Definition attr.c:49
char * agxget(void *obj, Agsym_t *sym)
Definition attr.c:457
Agiddisc_t AgIdDisc
Definition id.c:93
#define TAILPORT_ID
Definition cgraph.h:989
#define HEADPORT_ID
Definition cgraph.h:990
Agedge_t * agfstout(Agraph_t *g, Agnode_t *n)
Definition edge.c:28
#define aghead(e)
Definition cgraph.h:983
Agedge_t * agnxtout(Agraph_t *g, Agedge_t *e)
Definition edge.c:43
#define AGTAIL(e)
Definition cgraph.h:978
Agedge_t * agfstin(Agraph_t *g, Agnode_t *n)
Definition edge.c:59
#define AGHEAD(e)
Definition cgraph.h:979
int agisdirected(Agraph_t *g)
Definition graph.c:184
int agisstrict(Agraph_t *g)
Definition graph.c:194
int agwrite(Agraph_t *g, void *ofile)
Return 0 on success, EOF on failure.
Definition write.c:736
Agnode_t * agnxtnode(Agraph_t *g, Agnode_t *n)
Definition node.c:50
Agnode_t * agfstnode(Agraph_t *g)
Definition node.c:43
Agraph_t * agraphof(void *obj)
Definition obj.c:187
char * agnameof(void *)
returns a string descriptor for the object.
Definition id.c:145
#define AGID(obj)
returns the unique integer ID associated with the object
Definition cgraph.h:221
uint64_t IDTYPE
unique per main graph ID
Definition cgraph.h:73
#define AGTYPE(obj)
returns AGRAPH, AGNODE, or AGEDGE depending on the type of the object
Definition cgraph.h:216
#define AGATTRWF(obj)
Definition cgraph.h:226
Agraph_t * agroot(void *obj)
Definition obj.c:170
#define AGSEQ(obj)
Definition cgraph.h:225
@ AGOUTEDGE
Definition cgraph.h:207
@ AGEDGE
Definition cgraph.h:207
@ AGNODE
Definition cgraph.h:207
@ AGINEDGE
Definition cgraph.h:207
@ AGRAPH
Definition cgraph.h:207
int aghtmlstr(const char *)
Definition refstr.c:440
char * agstrcanon(char *arg, char *buf)
Definition write.c:226
int agstrfree(Agraph_t *, const char *, bool is_html)
Definition refstr.c:417
char * agstrdup(Agraph_t *, const char *)
returns a pointer to a reference-counted copy of the argument string, creating one if necessary
Definition refstr.c:401
Agraph_t * agparent(Agraph_t *g)
Definition subg.c:85
Agraph_t * agfstsubg(Agraph_t *g)
Definition subg.c:72
Agraph_t * agnxtsubg(Agraph_t *subg)
Definition subg.c:77
replacements for ctype.h functions
static bool gv_isalnum(int c)
Definition gv_ctype.h:43
static bool gv_isdigit(int c)
Definition gv_ctype.h:41
$2 prev
Definition htmlparse.y:291
textitem scanner parser str
Definition htmlparse.y:218
static Agedge_t * top(edge_stack_t *sp)
Definition tred.c:76
ViewInfo * view
Definition viewport.c:40
platform abstraction for case-insensitive string functions
string attribute container
Definition cgraph.h:633
char ** str
the attribute string values indexed by Agsym_s.id
Definition cgraph.h:636
Dict_t * dict
shared dict of Agsym_s to interpret Agattr_s.str
Definition cgraph.h:635
uint64_t seq[3]
Definition cgraph.h:414
Dict_t * n
Definition cgraph.h:656
struct Agdatadict_s::@32 dict
Dict_t * e
Definition cgraph.h:656
Dict_t * g
Definition cgraph.h:656
unsigned directed
Definition cgraph.h:285
graph or subgraph
Definition cgraph.h:424
Agclos_t * clos
shared resources
Definition cgraph.h:434
Agdesc_t desc
Definition cgraph.h:426
string attribute descriptor symbol in Agattr_s.dict
Definition cgraph.h:641
char * name
Definition cgraph.h:643
int id
index in Agattr_s.str
Definition cgraph.h:645
char * defval
Definition cgraph.h:644
unsigned char print
Definition cgraph.h:648
Definition cdt.h:98
int key
Definition cdt.h:85
Definition write.c:58
uint64_t * preorder_number
Definition write.c:59
Dt_t * edges_todo
dictionary (seq → pending_edge_t) of edges to write
Definition write.c:62
int level
Definition write.c:64
uint64_t * node_last_written
Definition write.c:61
bitarray_t edges_done
bitset of written edges by sequence ID
Definition write.c:63
an edge destined to be written out
Definition write.c:52
unsigned seq
sequence number
Definition write.c:54
Agedge_t * edge
the edge itself
Definition write.c:55
Dtlink_t link
data used by libcdt
Definition write.c:53
Definition types.h:48
static tok_t tok(const char *input, const char *separators)
begin tokenization of a new string
Definition tokenize.h:43
Definition grammar.c:90
abstraction for squashing compiler warnings for unused symbols
#define UNUSED
Definition unused.h:25
static int cmp(void *a, void *b)
Definition write.c:781
static bool is_anonymous(Agraph_t *g)
Definition write.c:392
static Agsym_t * Headport
Definition write.c:49
static int write_hdr(Agraph_t *g, iochan_t *ofile, bool top, info_t *info)
Definition write.c:329
static uint64_t subgdfs(Agraph_t *g, uint64_t ix, info_t *info)
Definition write.c:762
static int write_canonstr(Agraph_t *g, iochan_t *ofile, char *str, bool known)
Definition write.c:252
void iochan_t
Definition write.c:40
#define MIN_OUTPUTLINE
Definition write.c:47
static bool not_default_attrs(Agraph_t *g, Agnode_t *n)
Definition write.c:436
static void after_write(info_t)
Definition write.c:816
static int ioput(Agraph_t *g, iochan_t *ofile, char *str)
Definition write.c:42
static int write_trl(Agraph_t *g, iochan_t *ofile, info_t *info)
Definition write.c:380
static int write_edge(Agedge_t *e, iochan_t *ofile, Dict_t *d, info_t *info)
Definition write.c:636
static int attrs_written(void *obj)
Definition write.c:566
static int write_subgs(Agraph_t *g, iochan_t *ofile, info_t *info)
Definition write.c:450
static int write_edge_name(Agedge_t *e, iochan_t *ofile, bool terminate, info_t *info)
Definition write.c:467
static int write_dicts(Agraph_t *g, iochan_t *ofile, bool top, info_t *info)
Definition write.c:317
static int write_port(Agedge_t *e, iochan_t *ofile, Agsym_t *port)
Definition write.c:599
static void set_attrwf(Agraph_t *g, bool toplevel, bool value)
Definition write.c:717
static int write_nodename(Agnode_t *n, iochan_t *ofile)
Definition write.c:545
static bool is_escape(const char *str)
Definition write.c:88
static int indent(Agraph_t *g, iochan_t *ofile, const info_t info)
Definition write.c:72
static int write_body(Agraph_t *g, iochan_t *ofile, info_t *info)
Definition write.c:680
static int write_edges(iochan_t *ofile, Dict_t *d, info_t *info)
Definition write.c:668
static bool has_no_edges(Agraph_t *g, Agnode_t *n)
Definition write.c:432
static int write_nondefault_attrs(void *obj, iochan_t *ofile, Dict_t *defdict, info_t *info)
Definition write.c:492
static bool irrelevant_subgraph(Agraph_t *g)
Definition write.c:409
#define MAX_OUTPUTLINE
Definition write.c:46
static bool is_id_char(char c)
Definition write.c:82
static bool write_node_test(Agraph_t *g, Agnode_t *n, info_t *info)
Definition write.c:589
static info_t before_write(Agraph_t *)
Definition write.c:803
static int write_canonstr_(Agraph_t *g, iochan_t *ofile, char *str, bool chk)
Definition write.c:233
static int write_dict(Agraph_t *g, iochan_t *ofile, char *name, Dict_t *dict, bool top, info_t *info)
Definition write.c:268
static Agsym_t * Tailport
Definition write.c:49
#define EMPTY(s)
Definition write.c:38
static int write_node(Agraph_t *subg, Agnode_t *n, iochan_t *ofile, Dict_t *d, info_t *info)
Definition write.c:568
static Dtdisc_t edge_disc
the “discipline” for info_t.edges_todo
Definition write.c:794
static void * copy(void *p, Dtdisc_t *disc UNUSED)
Definition write.c:773
static char * agcanonhtmlstr(const char *arg, char *buf)
Definition write.c:217
static char * agstrcanon_(char *arg, char *buf)
Definition write.c:126
static int Max_outputline
Definition write.c:48