1 | /* $Id$ $Revision$ */ |
2 | /* vim:set shiftwidth=4 ts=8: */ |
3 | |
4 | /************************************************************************* |
5 | * Copyright (c) 2011 AT&T Intellectual Property |
6 | * All rights reserved. This program and the accompanying materials |
7 | * are made available under the terms of the Eclipse Public License v1.0 |
8 | * which accompanies this distribution, and is available at |
9 | * http://www.eclipse.org/legal/epl-v10.html |
10 | * |
11 | * Contributors: See CVS logs. Details at http://www.graphviz.org/ |
12 | *************************************************************************/ |
13 | |
14 | #include <cghdr.h> |
15 | |
16 | /* |
17 | * dynamic attributes |
18 | */ |
19 | |
20 | /* to create a graph's data dictionary */ |
21 | |
22 | #define MINATTR 4 /* minimum allocation */ |
23 | |
24 | static void freesym(Dict_t * d, void * obj, Dtdisc_t * disc); |
25 | |
26 | Dtdisc_t AgDataDictDisc = { |
27 | (int) offsetof(Agsym_t, name), /* use symbol name as key */ |
28 | -1, |
29 | (int) offsetof(Agsym_t, link), |
30 | NIL(Dtmake_f), |
31 | freesym, |
32 | NIL(Dtcompar_f), |
33 | NIL(Dthash_f) |
34 | }; |
35 | |
36 | static char DataDictName[] = "_AG_datadict" ; |
37 | static void init_all_attrs(Agraph_t * g); |
38 | static Agdesc_t ProtoDesc = { 1, 0, 1, 0, 1, 1 }; |
39 | static Agraph_t *ProtoGraph; |
40 | |
41 | Agdatadict_t *agdatadict(Agraph_t * g, int cflag) |
42 | { |
43 | Agdatadict_t *rv; |
44 | rv = (Agdatadict_t *) aggetrec(g, DataDictName, FALSE); |
45 | if (rv || !cflag) |
46 | return rv; |
47 | init_all_attrs(g); |
48 | rv = (Agdatadict_t *) aggetrec(g, DataDictName, FALSE); |
49 | return rv; |
50 | } |
51 | |
52 | Dict_t *agdictof(Agraph_t * g, int kind) |
53 | { |
54 | Agdatadict_t *dd; |
55 | Dict_t *dict; |
56 | |
57 | dd = agdatadict(g, FALSE); |
58 | if (dd) |
59 | switch (kind) { |
60 | case AGRAPH: |
61 | dict = dd->dict.g; |
62 | break; |
63 | case AGNODE: |
64 | dict = dd->dict.n; |
65 | break; |
66 | case AGINEDGE: |
67 | case AGOUTEDGE: |
68 | dict = dd->dict.e; |
69 | break; |
70 | default: |
71 | agerr(AGERR,"agdictof: unknown kind %d\n" , kind); |
72 | dict = NIL(Dict_t *); |
73 | break; |
74 | } else |
75 | dict = NIL(Dict_t *); |
76 | return dict; |
77 | } |
78 | |
79 | Agsym_t *agnewsym(Agraph_t * g, char *name, char *value, int id, int kind) |
80 | { |
81 | Agsym_t *sym; |
82 | sym = agalloc(g, sizeof(Agsym_t)); |
83 | sym->kind = kind; |
84 | sym->name = agstrdup(g, name); |
85 | sym->defval = agstrdup(g, value); |
86 | sym->id = id; |
87 | return sym; |
88 | } |
89 | |
90 | static void agcopydict(Dict_t * src, Dict_t * dest, Agraph_t * g, int kind) |
91 | { |
92 | Agsym_t *sym, *newsym; |
93 | |
94 | assert(dtsize(dest) == 0); |
95 | for (sym = (Agsym_t *) dtfirst(src); sym; |
96 | sym = (Agsym_t *) dtnext(src, sym)) { |
97 | newsym = agnewsym(g, sym->name, sym->defval, sym->id, kind); |
98 | newsym->print = sym->print; |
99 | newsym->fixed = sym->fixed; |
100 | dtinsert(dest, newsym); |
101 | } |
102 | } |
103 | |
104 | static Agdatadict_t *agmakedatadict(Agraph_t * g) |
105 | { |
106 | Agraph_t *par; |
107 | Agdatadict_t *parent_dd, *dd; |
108 | |
109 | dd = (Agdatadict_t *) agbindrec(g, DataDictName, sizeof(Agdatadict_t), |
110 | FALSE); |
111 | dd->dict.n = agdtopen(g, &AgDataDictDisc, Dttree); |
112 | dd->dict.e = agdtopen(g, &AgDataDictDisc, Dttree); |
113 | dd->dict.g = agdtopen(g, &AgDataDictDisc, Dttree); |
114 | if ((par = agparent(g))) { |
115 | parent_dd = agdatadict(par, FALSE); |
116 | assert(dd != parent_dd); |
117 | dtview(dd->dict.n, parent_dd->dict.n); |
118 | dtview(dd->dict.e, parent_dd->dict.e); |
119 | dtview(dd->dict.g, parent_dd->dict.g); |
120 | } else { |
121 | if (ProtoGraph && (g != ProtoGraph)) { |
122 | /* it's not ok to dtview here for several reasons. the proto |
123 | graph could change, and the sym indices don't match */ |
124 | parent_dd = agdatadict(ProtoGraph, FALSE); |
125 | agcopydict(parent_dd->dict.n, dd->dict.n, g, AGNODE); |
126 | agcopydict(parent_dd->dict.e, dd->dict.e, g, AGEDGE); |
127 | agcopydict(parent_dd->dict.g, dd->dict.g, g, AGRAPH); |
128 | } |
129 | } |
130 | return dd; |
131 | } |
132 | |
133 | /* look up an attribute with possible viewpathing */ |
134 | Agsym_t *agdictsym(Dict_t * dict, char *name) |
135 | { |
136 | Agsym_t key; |
137 | key.name = (char *) name; |
138 | return (Agsym_t *) dtsearch(dict, &key); |
139 | } |
140 | |
141 | /* look up attribute in local dictionary with no view pathing */ |
142 | Agsym_t *aglocaldictsym(Dict_t * dict, char *name) |
143 | { |
144 | Agsym_t *rv; |
145 | Dict_t *view; |
146 | |
147 | view = dtview(dict, NIL(Dict_t *)); |
148 | rv = agdictsym(dict, name); |
149 | dtview(dict, view); |
150 | return rv; |
151 | } |
152 | |
153 | Agsym_t *agattrsym(void *obj, char *name) |
154 | { |
155 | Agattr_t *data; |
156 | Agsym_t *rv; |
157 | char *arg = name; |
158 | |
159 | data = agattrrec((Agobj_t *) obj); |
160 | if (data) |
161 | rv = agdictsym(data->dict, arg); |
162 | else |
163 | rv = NILsym; |
164 | return rv; |
165 | } |
166 | |
167 | /* to create a graph's, node's edge's string attributes */ |
168 | |
169 | char *AgDataRecName = "_AG_strdata" ; |
170 | |
171 | static int topdictsize(Agobj_t * obj) |
172 | { |
173 | Dict_t *d; |
174 | |
175 | d = agdictof(agroot(agraphof(obj)), AGTYPE(obj)); |
176 | return d ? dtsize(d) : 0; |
177 | } |
178 | |
179 | /* g can be either the enclosing graph, or ProtoGraph */ |
180 | static Agrec_t *agmakeattrs(Agraph_t * context, void *obj) |
181 | { |
182 | int sz; |
183 | Agattr_t *rec; |
184 | Agsym_t *sym; |
185 | Dict_t *datadict; |
186 | |
187 | rec = agbindrec(obj, AgDataRecName, sizeof(Agattr_t), FALSE); |
188 | datadict = agdictof(context, AGTYPE(obj)); |
189 | assert(datadict); |
190 | if (rec->dict == NIL(Dict_t *)) { |
191 | rec->dict = agdictof(agroot(context), AGTYPE(obj)); |
192 | /* don't malloc(0) */ |
193 | sz = topdictsize(obj); |
194 | if (sz < MINATTR) |
195 | sz = MINATTR; |
196 | rec->str = agalloc(agraphof(obj), (size_t) sz * sizeof(char *)); |
197 | /* doesn't call agxset() so no obj-modified callbacks occur */ |
198 | for (sym = (Agsym_t *) dtfirst(datadict); sym; |
199 | sym = (Agsym_t *) dtnext(datadict, sym)) |
200 | rec->str[sym->id] = agstrdup(agraphof(obj), sym->defval); |
201 | } else { |
202 | assert(rec->dict == datadict); |
203 | } |
204 | return (Agrec_t *) rec; |
205 | } |
206 | |
207 | static void freeattr(Agobj_t * obj, Agattr_t * attr) |
208 | { |
209 | int i, sz; |
210 | Agraph_t *g; |
211 | |
212 | g = agraphof(obj); |
213 | sz = topdictsize(obj); |
214 | for (i = 0; i < sz; i++) |
215 | agstrfree(g, attr->str[i]); |
216 | agfree(g, attr->str); |
217 | } |
218 | |
219 | static void freesym(Dict_t * d, void * obj, Dtdisc_t * disc) |
220 | { |
221 | Agsym_t *sym; |
222 | |
223 | NOTUSED(d); |
224 | sym = (Agsym_t *) obj; |
225 | NOTUSED(disc); |
226 | agstrfree(Ag_G_global, sym->name); |
227 | agstrfree(Ag_G_global, sym->defval); |
228 | agfree(Ag_G_global, sym); |
229 | } |
230 | |
231 | Agattr_t *agattrrec(void *obj) |
232 | { |
233 | return (Agattr_t *) aggetrec(obj, AgDataRecName, FALSE); |
234 | } |
235 | |
236 | |
237 | static void addattr(Agraph_t * g, Agobj_t * obj, Agsym_t * sym) |
238 | { |
239 | Agattr_t *attr; |
240 | |
241 | attr = (Agattr_t *) agattrrec(obj); |
242 | assert(attr != NIL(Agattr_t *)); |
243 | if (sym->id >= MINATTR) |
244 | attr->str = (char **) AGDISC(g, mem)->resize(AGCLOS(g, mem), |
245 | attr->str, |
246 | sym->id * |
247 | sizeof(char *), |
248 | (sym->id + |
249 | 1) * sizeof(char *)); |
250 | attr->str[sym->id] = agstrdup(g, sym->defval); |
251 | /* agmethod_upd(g,obj,sym); JCE and GN didn't like this. */ |
252 | } |
253 | |
254 | |
255 | static Agsym_t *setattr(Agraph_t * g, int kind, char *name, char *value) |
256 | { |
257 | Agdatadict_t *dd; |
258 | Dict_t *ldict, *rdict; |
259 | Agsym_t *lsym, *psym, *rsym, *rv; |
260 | Agraph_t *root; |
261 | Agnode_t *n; |
262 | Agedge_t *e; |
263 | |
264 | assert(value); |
265 | root = agroot(g); |
266 | dd = agdatadict(g, TRUE); /* force initialization of string attributes */ |
267 | ldict = agdictof(g, kind); |
268 | lsym = aglocaldictsym(ldict, name); |
269 | if (lsym) { /* update old local definition */ |
270 | agstrfree(g, lsym->defval); |
271 | lsym->defval = agstrdup(g, value); |
272 | rv = lsym; |
273 | } else { |
274 | psym = agdictsym(ldict, name); /* search with viewpath up to root */ |
275 | if (psym) { /* new local definition */ |
276 | lsym = agnewsym(g, name, value, psym->id, kind); |
277 | dtinsert(ldict, lsym); |
278 | rv = lsym; |
279 | } else { /* new global definition */ |
280 | rdict = agdictof(root, kind); |
281 | rsym = agnewsym(g, name, value, dtsize(rdict), kind); |
282 | dtinsert(rdict, rsym); |
283 | switch (kind) { |
284 | case AGRAPH: |
285 | agapply(root, (Agobj_t *) root, (agobjfn_t) addattr, |
286 | rsym, TRUE); |
287 | break; |
288 | case AGNODE: |
289 | for (n = agfstnode(root); n; n = agnxtnode(root, n)) |
290 | addattr(g, (Agobj_t *) n, rsym); |
291 | break; |
292 | case AGINEDGE: |
293 | case AGOUTEDGE: |
294 | for (n = agfstnode(root); n; n = agnxtnode(root, n)) |
295 | for (e = agfstout(root, n); e; e = agnxtout(root, e)) |
296 | addattr(g, (Agobj_t *) e, rsym); |
297 | break; |
298 | } |
299 | rv = rsym; |
300 | } |
301 | } |
302 | if (rv && (kind == AGRAPH)) |
303 | agxset(g, rv, value); |
304 | agmethod_upd(g, g, rv); /* JCE and GN wanted this */ |
305 | return rv; |
306 | } |
307 | |
308 | static Agsym_t *getattr(Agraph_t * g, int kind, char *name) |
309 | { |
310 | Agsym_t *rv = 0; |
311 | Dict_t *dict; |
312 | dict = agdictof(g, kind); |
313 | if (dict) |
314 | rv = agdictsym(dict, name); /* viewpath up to root */ |
315 | return rv; |
316 | } |
317 | |
318 | /* |
319 | * create or update an existing attribute and return its descriptor. |
320 | * if the new value is NIL(char*), this is only a search, no update. |
321 | * when a new attribute is created, existing graphs/nodes/edges |
322 | * receive its default value. |
323 | */ |
324 | Agsym_t *agattr(Agraph_t * g, int kind, char *name, char *value) |
325 | { |
326 | Agsym_t *rv; |
327 | |
328 | if (g == 0) { |
329 | if (ProtoGraph == 0) |
330 | ProtoGraph = agopen(0, ProtoDesc, 0); |
331 | g = ProtoGraph; |
332 | } |
333 | if (value) |
334 | rv = setattr(g, kind, name, value); |
335 | else |
336 | rv = getattr(g, kind, name); |
337 | return rv; |
338 | } |
339 | |
340 | Agsym_t *agnxtattr(Agraph_t * g, int kind, Agsym_t * attr) |
341 | { |
342 | Dict_t *d; |
343 | Agsym_t *rv; |
344 | |
345 | if ((d = agdictof(g, kind))) { |
346 | if (attr) |
347 | rv = (Agsym_t *) dtnext(d, attr); |
348 | else |
349 | rv = (Agsym_t *) dtfirst(d); |
350 | } else |
351 | rv = 0; |
352 | return rv; |
353 | } |
354 | |
355 | /* Create or delete attributes associated with an object */ |
356 | |
357 | void agraphattr_init(Agraph_t * g) |
358 | { |
359 | /* Agdatadict_t *dd; */ |
360 | /* Agrec_t *attr; */ |
361 | Agraph_t *context; |
362 | |
363 | g->desc.has_attrs = 1; |
364 | /* dd = */ agmakedatadict(g); |
365 | if (!(context = agparent(g))) |
366 | context = g; |
367 | /* attr = */ agmakeattrs(context, g); |
368 | } |
369 | |
370 | int agraphattr_delete(Agraph_t * g) |
371 | { |
372 | Agdatadict_t *dd; |
373 | Agattr_t *attr; |
374 | |
375 | Ag_G_global = g; |
376 | if ((attr = agattrrec(g))) { |
377 | freeattr((Agobj_t *) g, attr); |
378 | agdelrec(g, attr->h.name); |
379 | } |
380 | |
381 | if ((dd = agdatadict(g, FALSE))) { |
382 | if (agdtclose(g, dd->dict.n)) return 1; |
383 | if (agdtclose(g, dd->dict.e)) return 1; |
384 | if (agdtclose(g, dd->dict.g)) return 1; |
385 | agdelrec(g, dd->h.name); |
386 | } |
387 | return 0; |
388 | } |
389 | |
390 | void agnodeattr_init(Agraph_t * g, Agnode_t * n) |
391 | { |
392 | Agattr_t *data; |
393 | |
394 | data = agattrrec(n); |
395 | if ((!data) || (!data->dict)) |
396 | (void) agmakeattrs(g, n); |
397 | } |
398 | |
399 | void agnodeattr_delete(Agnode_t * n) |
400 | { |
401 | Agattr_t *rec; |
402 | |
403 | if ((rec = agattrrec(n))) { |
404 | freeattr((Agobj_t *) n, rec); |
405 | agdelrec(n, AgDataRecName); |
406 | } |
407 | } |
408 | |
409 | void agedgeattr_init(Agraph_t * g, Agedge_t * e) |
410 | { |
411 | Agattr_t *data; |
412 | |
413 | data = agattrrec(e); |
414 | if ((!data) || (!data->dict)) |
415 | (void) agmakeattrs(g, e); |
416 | } |
417 | |
418 | void agedgeattr_delete(Agedge_t * e) |
419 | { |
420 | Agattr_t *rec; |
421 | |
422 | if ((rec = agattrrec(e))) { |
423 | freeattr((Agobj_t *) e, rec); |
424 | agdelrec(e, AgDataRecName); |
425 | } |
426 | } |
427 | |
428 | char *agget(void *obj, char *name) |
429 | { |
430 | Agsym_t *sym; |
431 | Agattr_t *data; |
432 | char *rv; |
433 | |
434 | sym = agattrsym(obj, name); |
435 | if (sym == NILsym) |
436 | rv = 0; /* note was "", but this provides more info */ |
437 | else { |
438 | data = agattrrec((Agobj_t *) obj); |
439 | rv = (char *) (data->str[sym->id]); |
440 | } |
441 | return rv; |
442 | } |
443 | |
444 | char *agxget(void *obj, Agsym_t * sym) |
445 | { |
446 | Agattr_t *data; |
447 | char *rv; |
448 | |
449 | data = agattrrec((Agobj_t *) obj); |
450 | assert((sym->id >= 0) && (sym->id < topdictsize(obj))); |
451 | rv = (char *) (data->str[sym->id]); |
452 | return rv; |
453 | } |
454 | |
455 | int agset(void *obj, char *name, char *value) |
456 | { |
457 | Agsym_t *sym; |
458 | int rv; |
459 | |
460 | sym = agattrsym(obj, name); |
461 | if (sym == NILsym) |
462 | rv = FAILURE; |
463 | else |
464 | rv = agxset(obj, sym, value); |
465 | return rv; |
466 | } |
467 | |
468 | int agxset(void *obj, Agsym_t * sym, char *value) |
469 | { |
470 | Agraph_t *g; |
471 | Agobj_t *hdr; |
472 | Agattr_t *data; |
473 | Agsym_t *lsym; |
474 | |
475 | g = agraphof(obj); |
476 | hdr = (Agobj_t *) obj; |
477 | data = agattrrec(hdr); |
478 | assert((sym->id >= 0) && (sym->id < topdictsize(obj))); |
479 | agstrfree(g, data->str[sym->id]); |
480 | data->str[sym->id] = agstrdup(g, value); |
481 | if (hdr->tag.objtype == AGRAPH) { |
482 | /* also update dict default */ |
483 | Dict_t *dict; |
484 | dict = agdatadict(g, FALSE)->dict.g; |
485 | if ((lsym = aglocaldictsym(dict, sym->name))) { |
486 | agstrfree(g, lsym->defval); |
487 | lsym->defval = agstrdup(g, value); |
488 | } else { |
489 | lsym = agnewsym(g, sym->name, value, sym->id, AGTYPE(hdr)); |
490 | dtinsert(dict, lsym); |
491 | } |
492 | } |
493 | agmethod_upd(g, obj, sym); |
494 | return SUCCESS; |
495 | } |
496 | |
497 | int agsafeset(void *obj, char *name, char *value, char *def) |
498 | { |
499 | Agsym_t *a; |
500 | |
501 | a = agattr(agraphof(obj), AGTYPE(obj), name, 0); |
502 | if (!a) |
503 | a = agattr(agraphof(obj), AGTYPE(obj), name, def); |
504 | return agxset(obj, a, value); |
505 | } |
506 | |
507 | |
508 | /* |
509 | * attach attributes to the already created graph objs. |
510 | * presumably they were already initialized, so we don't invoke |
511 | * any of the old methods. |
512 | */ |
513 | static void init_all_attrs(Agraph_t * g) |
514 | { |
515 | Agraph_t *root; |
516 | Agnode_t *n; |
517 | Agedge_t *e; |
518 | |
519 | root = agroot(g); |
520 | agapply(root, (Agobj_t *) root, (agobjfn_t) agraphattr_init, |
521 | NIL(Agdisc_t *), TRUE); |
522 | for (n = agfstnode(root); n; n = agnxtnode(root, n)) { |
523 | agnodeattr_init(g, n); |
524 | for (e = agfstout(root, n); e; e = agnxtout(root, e)) { |
525 | agedgeattr_init(g, e); |
526 | } |
527 | } |
528 | } |
529 | |
530 | /* agcopyattr: |
531 | * Assumes attributes have already been declared. |
532 | * Do not copy key attribute for edges, as this must be distinct. |
533 | * Returns non-zero on failure or if objects have different type. |
534 | */ |
535 | int agcopyattr(void *oldobj, void *newobj) |
536 | { |
537 | Agraph_t *g; |
538 | Agsym_t *sym; |
539 | Agsym_t *newsym; |
540 | char* val; |
541 | char* nval; |
542 | int r = 1; |
543 | |
544 | g = agraphof(oldobj); |
545 | if (AGTYPE(oldobj) != AGTYPE(newobj)) |
546 | return 1; |
547 | sym = 0; |
548 | while ((sym = agnxtattr(g, AGTYPE(oldobj), sym))) { |
549 | newsym = agattrsym(newobj, sym->name); |
550 | if (!newsym) |
551 | return 1; |
552 | val = agxget(oldobj, sym); |
553 | r = agxset(newobj, newsym, val); |
554 | /* FIX(?): Each graph has its own string cache, so a whole new refstr is possibly |
555 | * allocated. If the original was an html string, make sure the new one is as well. |
556 | * If cgraph goes to single string table, this can be removed. |
557 | */ |
558 | if (aghtmlstr (val)) { |
559 | nval = agxget (newobj, newsym); |
560 | agmarkhtmlstr (nval); |
561 | } |
562 | } |
563 | return r; |
564 | } |
565 | |