1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
(** Reading XDot files *)
open Dot_ast
open Printf
type pos = float * float
type bounding_box = pos * pos
type node_layout = {
n_name : string;
n_pos : pos;
n_bbox : bounding_box;
n_draw : XDotDraw.operation list;
n_ldraw : XDotDraw.operation list;
}
type cluster_layout = {
c_pos : pos;
c_bbox : bounding_box;
c_draw : XDotDraw.operation list;
c_ldraw : XDotDraw.operation list;
}
type edge_layout = {
e_draw : XDotDraw.operation list;
e_ldraw : XDotDraw.operation list;
e_hdraw : XDotDraw.operation list;
e_tdraw : XDotDraw.operation list;
e_hldraw : XDotDraw.operation list;
e_tldraw : XDotDraw.operation list;
}
let mk_node_layout ~name ~pos ~bbox ~draw ~ldraw =
{ n_name = name;
n_pos = pos;
n_bbox = bbox;
n_draw = draw;
n_ldraw = ldraw }
let mk_cluster_layout ~pos ~bbox ~draw ~ldraw =
{ c_pos = pos;
c_bbox = bbox;
c_draw = draw;
c_ldraw = ldraw }
let mk_edge_layout ~draw ~ldraw ~hdraw ~tdraw ~hldraw ~tldraw =
{ e_draw = draw;
e_ldraw = ldraw;
e_hdraw = hdraw;
e_tdraw = tdraw;
e_hldraw = hldraw;
e_tldraw = tldraw;
}
exception ParseError of string
let read_pos s = Scanf.sscanf s "%f,%f" (fun x y -> x, -.y)
let bounding_box (x, y) w h =
let lower_left = x -. w , y -. h in
let upper_right = x+.w,y+.h in
lower_left,upper_right
let get_dot_string = function
| Dot_ast.String s -> s
| Dot_ast.Ident s -> s
| Dot_ast.Number s -> s
| Dot_ast.Html s -> s
(** Finds the attributes [pos], [width] and [height] of a node
in the attribute list *)
let read_common_layout mk_layout attr_list =
let fold ((p,w,h, draw,ldraw) as attrs) = function
| (Dot_ast.Ident "pos"), Some (Dot_ast.String s) ->
(Some s), w, h, draw,ldraw
| (Dot_ast.Ident "width"), Some (Dot_ast.String s) ->
p, (Some s), h, draw,ldraw
| (Dot_ast.Ident "height"), Some (Dot_ast.String s) ->
p, w, (Some s), draw,ldraw
| (Dot_ast.Ident "_draw_"), Some (Dot_ast.String draw) ->
p,w,h, XDotDraw.parse draw, ldraw
| (Dot_ast.Ident "_ldraw_"), Some (Dot_ast.String ldraw) ->
p,w,h, draw, XDotDraw.parse ldraw
| _ -> attrs in
let fold_attr acc attr_list =
List.fold_left fold acc attr_list in
let attrs = List.fold_left fold_attr (None, None, None, [], [])
attr_list in
match attrs with
| Some pos, Some w, Some h, draw,ldraw->
let pos = read_pos pos in
let coord = bounding_box pos
(float_of_string w) (-.(float_of_string h)) in
mk_layout ~pos ~bbox:coord ~draw ~ldraw
| _,_,_, draw, ldraw ->
let pos = (0.,0.) in
let bbox = (0.,0.),(0.,0.) in
mk_layout ~pos ~bbox ~draw ~ldraw
let read_node_layout (id,_) attrs =
let f = read_common_layout
(fun ~pos ~bbox ~draw ~ldraw -> mk_node_layout ~pos ~bbox ~draw ~ldraw)
attrs in
f ~name:(get_dot_string id)
let read_cluster_layout = read_common_layout mk_cluster_layout
(** Reads the spline control points of a curve in an xdot file
example : "c 5 -black B 4 65 296 65 288 65 279 65 270 "
*)
(** Gets the layout of an edge out of the dot ast *)
let read_edge_layout attr_list =
let draw = ref [] in
let ldraw = ref [] in
let hdraw = ref [] in
let tdraw = ref [] in
let hldraw = ref [] in
let tldraw = ref [] in
let fill_draw_ops = function
| (Dot_ast.Ident "_draw_"), Some (Dot_ast.String s) ->
draw := XDotDraw.parse s
| (Dot_ast.Ident "_ldraw_"), Some (Dot_ast.String s) ->
ldraw := XDotDraw.parse s
| (Dot_ast.Ident "_hdraw_"), Some (Dot_ast.String s) ->
hdraw := XDotDraw.parse s
| (Dot_ast.Ident "_tdraw_"), Some (Dot_ast.String s) ->
tdraw := XDotDraw.parse s
| (Dot_ast.Ident "_hldraw_"), Some (Dot_ast.String s) ->
hldraw := XDotDraw.parse s
| (Dot_ast.Ident "_tldraw_"), Some (Dot_ast.String s) ->
tldraw := XDotDraw.parse s
| _ -> () in
List.iter (List.iter fill_draw_ops) attr_list;
let draw, ldraw = !draw, !ldraw in
let hdraw, tdraw, hldraw, tldraw = !hdraw, !tdraw, !hldraw, !tldraw in
mk_edge_layout ~draw ~ldraw ~hdraw ~tdraw ~hldraw ~tldraw
let read_bounding_box str =
let x1,y1,x2,y2 = Scanf.sscanf str "%f,%f,%f,%f" (fun a b c d -> a,b,c,d) in
let lower_left = (x1, -.y2) and upper_right = x2, -.y1 in
lower_left,upper_right
module Make(G : Graphviz.GraphWithDotAttrs) = struct
module HV = Hashtbl.Make(G.V)
module HE =
Map.Make
(struct
type t = G.E.t
let compare = G.E.compare
end)
module HT =
Hashtbl.Make
(Util.HTProduct
(Util.HTProduct(G.V)(G.V))
(struct type t = string let equal = (=) let hash = Hashtbl.hash end))
type graph_layout =
{ vertex_layouts : node_layout HV.t;
edge_layouts : edge_layout HE.t;
cluster_layouts : (string, cluster_layout) Hashtbl.t;
bbox : bounding_box }
exception Found of string
let e =
let al = G.edge_attributes e in
try
List.iter (function `Comment c -> raise (Found c) | _ -> ()) al;
None
with Found c ->
Some c
let (al : Dot_ast.attr list) =
try
List.iter
(List.iter
(function
| Ident "comment", Some c -> raise (Found (get_dot_string c))
| _ -> ()))
al;
""
with Found c ->
c
let strip_quotes = function
| "" -> ""
| s ->
let len = String.length s in
if s.[0] = '"' && s.[len -1] = '"' then String.sub s 1 (len - 2)
else s
let parse_graph_attr id conv stmts =
let read_attr = function
| Ident ident , Some (String attr) when ident = id ->
raise (Found attr)
| _ -> ()
in
let read_stmt = function
| Attr_graph attrs -> List.iter (List.iter read_attr) attrs
| _ -> ()
in
try
List.iter read_stmt stmts;
failwith ("Could not find the graph attribute named " ^ id)
with Found attr ->
conv attr
let parse_bounding_box = parse_graph_attr "bb" read_bounding_box
let parse_layouts g stmts =
let name_to_vertex = Hashtbl.create 97 in
let = HT.create 97 in
let vertex_layouts = HV.create 97 in
let edge_layouts = ref HE.empty in
let cluster_layouts = Hashtbl.create 97 in
G.iter_vertex
(fun v ->
let name = strip_quotes (G.vertex_name v) in
Hashtbl.add name_to_vertex name v)
g;
G.iter_edges_e
(fun e ->
let = match get_edge_comment e with
| Some c -> strip_quotes c
| None -> ""
in
let vs = G.E.src e, G.E.dst e in
HT.add vertices_comment_to_edge (vs, comment) e)
g;
let find_vertex (id,_) =
let name = get_dot_string id in
try Hashtbl.find name_to_vertex name
with Not_found -> failwith ("Could not find vertex named " ^ name)
in
let find_edge v v' =
try HT.find vertices_comment_to_edge ((v, v'), comment)
with Not_found ->
raise Not_found
in
let rec collect_layouts cluster stmt =
try
match stmt with
| Node_stmt (node_id, al) ->
let v = find_vertex node_id in
HV.add vertex_layouts v (read_node_layout node_id al)
| Edge_stmt (NodeId id, [NodeId id'], al) ->
let v = find_vertex id in
let v' = find_vertex id' in
let = get_dot_comment al in
let e = find_edge v v' comment in
edge_layouts := HE.add e (read_edge_layout al) !edge_layouts
| Subgraph (SubgraphDef (Some id, stmts)) ->
let cluster = get_dot_string id in
List.iter (collect_layouts (Some cluster)) stmts
| Subgraph (SubgraphDef (_, stmts)) ->
List.iter (collect_layouts cluster) stmts
| Attr_graph al ->
(match cluster with
| Some c -> Hashtbl.add cluster_layouts c (read_cluster_layout al)
| None -> ())
| _ -> ()
with Not_found ->
()
in
List.iter (collect_layouts None) stmts;
vertex_layouts, edge_layouts, cluster_layouts
let parse g dot_ast =
let v_layouts, e_layouts, c_layouts = parse_layouts g dot_ast.stmts in
let bbox = parse_bounding_box dot_ast.stmts in
{ vertex_layouts = v_layouts;
edge_layouts = !e_layouts;
cluster_layouts = c_layouts;
bbox = bbox }
exception DotError of string
let layout_of_xdot ~xdot_file g =
let dot_ast = Dot.parse_dot_ast xdot_file in
parse g dot_ast
let layout_of_dot ?(cmd="dot") ~dot_file g =
let base_name =
try Filename.basename (Filename.chop_extension dot_file)
with Invalid_argument _ -> dot_file
in
let xdot_file = Filename.temp_file base_name ".xdot" in
let dot_cmd = sprintf "%s -Txdot %s > %s" cmd dot_file xdot_file in
match Sys.command dot_cmd with
| 0 ->
let l = layout_of_xdot ~xdot_file g in
Sys.remove xdot_file;
l
| _ ->
Sys.remove xdot_file;
raise (DotError "Error during dot execution")
end