30
31:- module(rdf_graphviz,
32 [ gviz_write_rdf/3 33 ]).
34:- use_module(library(semweb/rdf_db)).
35:- use_module(library(semweb/rdfs)).
36:- use_module(library(http/http_dispatch)).
37:- use_module(library(http/http_path)).
38:- use_module(library(http/html_write)).
39:- use_module(library(http/url_cache)).
40:- use_module(library(assoc)).
41:- use_module(library(option)).
42:- use_module(library(gensym)).
43:- use_module(library(lists)).
44:- use_module(library(apply)).
45:- use_module(library(ugraphs)).
46:- use_module(library(semweb/rdf_label)).
47
48:- rdf_register_ns(graphviz, 'http://www.graphviz.org/').
49
58
122
123:- meta_predicate gviz_write_rdf(+,+,:).
124:- rdf_meta gviz_write_rdf(+,t,t).
125
126gviz_write_rdf(Stream, Graph0, Options0) :-
127 exclude(exclude_triple, Graph0, Graph1),
128 meta_options(is_meta, Options0, Options),
129 format(Stream, 'digraph G~n{ ', []),
130 option(graph_attributes(Attrs), Options, []),
131 write_graph_attributes(Attrs, Stream),
132 smash_graph(Graph1, Graph2, Options),
133 combine_bags(Graph2, Triples, Bags, Options),
134 gv_write_edges(Triples, Done, Stream, Options),
135 assoc_to_list(Done, Nodes),
136 gv_write_nodes(Nodes, Stream, [bag_assoc(Bags)|Options]),
137 format(Stream, '~n}~n', []).
138
139is_meta(wrap_url).
140is_meta(shape_hook).
141is_meta(bag_shape_hook).
142is_meta(label_hook).
143
147
148write_graph_attributes([], _).
149write_graph_attributes([H|T], Out) :-
150 write_graph_attribute(H, Out),
151 write_graph_attributes(T, Out).
152
153write_graph_attribute(size(W,H), Out) :- !,
154 format(Out, ' size="~w,~w";~n', [W, H]).
155write_graph_attribute(AttVal, Out) :-
156 AttVal =.. [Name, Value],
157 format(Out, ' ~w="~w";~n', [Name, Value]).
158
159
165
166combine_bags(Graph, Graph, Bags, Options) :-
167 option(bags(graph), Options), !,
168 empty_assoc(Bags).
169combine_bags(Graph, Triples, Bags, _Options) :-
170 empty_assoc(Bags0),
171 find_bags(Graph, Graph1, Bags0, Bags1),
172 collect_bags(Graph1, Triples, Bags1, Bags).
173
174:- rdf_meta find_bags(t, -, +, -).
175
176find_bags([], [], Bags, Bags).
177find_bags([rdf(S,rdf:type,rdf:'Bag')|Graph], Triples, Bags0, Bags) :- !,
178 put_assoc(S, Bags0, [], Bags1),
179 find_bags(Graph, Triples, Bags1, Bags).
180find_bags([H|T0], [H|T], Bags0, Bags) :-
181 find_bags(T0, T, Bags0, Bags).
182
183collect_bags([], [], Bags, Bags).
184collect_bags([rdf(S,P,O)|Graph], Triples, Bags0, Bags) :-
185 bagid_property(P, _),
186 get_assoc(S, Bags0, L, Bags1, [O|L]), !,
187 collect_bags(Graph, Triples, Bags1, Bags).
188collect_bags([H|T0], [H|T], Bags0, Bags) :-
189 collect_bags(T0, T, Bags0, Bags).
190
191
196
197bagid_property(P, I) :-
198 atom(P), !,
199 string_concat('_:', N, P),
200 number_string(I, N),
201 integer(I), I >= 0.
202bagid_property(P, I) :-
203 atom_concat('_:', I, P).
204
208
209smash_graph(GraphIn, GraphOut, Options) :-
210 option(smash(Props), Options, []), !,
211 smash_graph_(Props, GraphIn, GraphOut).
212
213smash_graph_([], Graph, Graph).
214smash_graph_([H|T], Graph0, Graph) :-
215 smash_on_property(H, Graph0, Graph1),
216 smash_graph_(T, Graph1, Graph).
217
221
222smash_on_property(P, GraphIn, GraphOut) :-
223 smash_edges(GraphIn, P, Edges, Rest),
224 vertices_edges_to_ugraph([], Edges, Graph),
225 partition_ugraph(Graph, VerticeSets),
226 make_eq_bags(VerticeSets, VerticeBags, MapAssoc),
227 maplist(smash_triple(MapAssoc), Rest, Mapped),
228 append(Mapped, VerticeBags, GraphOut).
229
230smash_edges([], _, [], []).
231smash_edges([rdf(S,P,O)|T0], P, [S-O,O-S|T], Rest) :- !,
232 smash_edges(T0, P, T, Rest).
233smash_edges([H|T0], P, Edges, [H|T]) :-
234 smash_edges(T0, P, Edges, T).
235
236partition_ugraph([], []) :- !.
237partition_ugraph(G0, [Vs0|Vs]) :-
238 G0 = [V-_|_],
239 reachable(V, G0, Vs0),
240 del_vertices(G0, Vs0, G1),
241 partition_ugraph(G1, Vs).
242
243make_eq_bags(Vertices, Bags, MapAssoc) :-
244 make_eq_bags(Vertices, 1, Bags, Mapping),
245 list_to_assoc(Mapping, MapAssoc).
246
247:- rdf_meta make_eq_bags(+, +, t, -).
248
249make_eq_bags([], _, [], []).
250make_eq_bags([Vs|T0], I, [rdf(BagId, rdf:type, rdf:'Bag')|Bags], Mapping) :-
251 atom_concat('_:sameAs', I, BagId),
252 make_eq_bag(Vs, 1, BagId, Bags, BagsT),
253 make_mapping(Vs, BagId, Mapping, MappingT),
254 I2 is I + 1,
255 make_eq_bags(T0, I2, BagsT, MappingT).
256
257make_eq_bag([], _, _, Triples, Triples).
258make_eq_bag([H|T], I, BagId, [rdf(BagId, P, H)|Triples0], Triples) :-
259 bagid_property(P, I),
260 I2 is I + 1,
261 make_eq_bag(T, I2, BagId, Triples0, Triples).
262
263make_mapping([], _, Mapping, Mapping).
264make_mapping([H|T], BagId, [H-BagId|Mapping0], Mapping) :-
265 make_mapping(T, BagId, Mapping0, Mapping).
266
267smash_triple(Mapping, rdf(S0,P,O0), rdf(S,P,O)) :-
268 smash(Mapping, S0, S),
269 smash(Mapping, O0, O).
270
271smash(Assoc, R0, R) :-
272 get_assoc(R0, Assoc, R), !.
273smash(_, R, R).
274
275
282
283gv_write_edges(Graph, Done, Stream, Options) :-
284 empty_assoc(Done0),
285 gv_write_edges(Graph, Done0, Done, Stream, Options).
286
287gv_write_edges([], Done, Done, _, _).
288gv_write_edges([Triple|T], Done0, Done, Stream, Options) :-
289 write_edge(Triple, Done0, Done1, Stream, Options),
290 gv_write_edges(T, Done1, Done, Stream, Options).
291
292write_edge(rdf(S,P,O), Done0, Done2, Stream, Options) :-
293 format(Stream, ' ', []),
294 write_node_id(S, Done0, Done1, Stream),
295 write(Stream, ' -> '),
296 write_node_id(O, Done1, Done2, Stream),
297 resource_label(P, Label, Options),
298 ( option(edge_links(true), Options, true)
299 -> wrap_url(P, URL, Options),
300 target_option([href(URL), label(Label)], Attrs, Options),
301 write_attributes(Attrs, Stream)
302 ; write_attributes([label(Label)], Stream)
303 ),
304 nl(Stream).
305
306write_node_id(S, Done, Done, Stream) :-
307 get_assoc(S, Done, Id), !,
308 write(Stream, Id).
309write_node_id(S, Done0, Done, Stream) :-
310 gensym(n, Id),
311 put_assoc(S, Done0, Id, Done),
312 write(Stream, Id).
313
318
319gv_write_nodes([], _, _).
320gv_write_nodes([RDF-ID|T], Stream, Options) :-
321 format(Stream, '~w ', [ID]),
322 write_node_attributes(RDF, Stream, Options),
323 write(Stream, ';\n '),
324 gv_write_nodes(T, Stream, Options).
325
330
331write_node_attributes(R, Stream, Options) :-
332 rdf_is_resource(R),
333 option(bag_assoc(Bags), Options),
334 get_assoc(R, Bags, Members), !,
335 Members = [First|_],
336 shape(First, MemberShape0, Options),
337 bag_shape(Members, BagShape0, Options),
338 exclude(no_bag_option, MemberShape0, MemberShape),
339 option(bags(merge(BagShape1, Max0)), Options,
340 merge([ shape(box),
341 style('rounded,filled,bold'),
342 fillcolor('#ffff80')
343 ], 5)),
344 select_option(max(Max), BagShape0, BagShape2, Max0),
345 partition(label_option, BagShape2, LabelOptions0, BagShape2a),
346 merge_options(BagShape1, MemberShape, BagShape3),
347 merge_options(BagShape2a, BagShape3, BagShape),
348 merge_options(LabelOptions0, Options, LabelOptions),
349 bag_label(Members, Max, Label, LabelOptions),
350 write_attributes([html(Label)|BagShape], Stream).
351write_node_attributes(R, Stream, Options) :-
352 rdf_is_resource(R), !,
353 shape(R, Shape, Options),
354 wrap_url(R, URL, Options),
355 resource_label(R, Label, Options),
356 target_option([href(URL), label(Label)|Shape], Attrs, Options),
357 ( select(img(IMGOptions), Attrs, RAttrs),
358 catch(write_image_node(IMGOptions, RAttrs, Stream, Options),
359 error(existence_error(url,URL2),Context),
360 ( print_message(warning,
361 error(existence_error(url,URL2),Context)),
362 fail))
363 -> true
364 ; delete(Attrs, img(_), RAttrs),
365 write_attributes(RAttrs, Stream)
366 ).
367write_node_attributes(Lit, Stream, Options) :-
368 shape(Lit, Shape, Options),
369 option(max_label_length(MaxLen), Options, 25),
370 literal_text(Lit, Text),
371 truncate_atom(Text, MaxLen, Summary0),
372 ( ( option(display_lang(true), Options, true),
373 Lit = literal(lang(Lang, _)))
374 -> atomic_list_concat([Summary0, '@', Lang], Summary)
375 ; Summary = Summary0
376 ),
377 write_attributes([label(Summary)|Shape], Stream).
378
379target_option(Attrs0, Attrs, Options) :-
380 option(target(Target), Options), !,
381 Attrs = [target(Target)|Attrs0].
382target_option(Attrs, Attrs, _).
383
384no_bag_option(img(_)).
385no_bag_option(width(_)).
386no_bag_option(height(_)).
387no_bag_option(cellpadding(_)).
388no_bag_option(fixedsize(_)).
389no_bag_option(label(_)).
390no_bag_option(border(_)).
391
392label_option(max_label_length(_)).
393
401
402bag_label(Members, Max, Label, Options) :-
403 length(Members, Len),
404 phrase(html(table([ border(0) ],
405 \html_bag_label(Members, 1, Max, Len, Options))),
406 Tokens),
407 with_output_to(string(Label), print_html(Tokens)).
408
409html_bag_label([], _, _, _, _) --> !.
410html_bag_label(_, I, Max, Len, _Options) -->
411 { I > Max }, !,
412 html(tr(td([align(right), cellpadding(5)],
413 font(face('Helvetica:style=Italic'), '... showing ~D of ~D'-[Max, Len])))).
414html_bag_label([H|T], I, Max, Len, Options) -->
415 { ( atom(H)
416 -> wrap_url(H, URL, Options),
417 target_option([href(URL)], Atts, Options)
418 ; Atts=[]
419 )
420 },
421 html(tr(td([align(left)|Atts], \html_resource_label(H, Options)))),
422 { I2 is I + 1 },
423 html_bag_label(T, I2, Max, Len, Options).
424
425html_resource_label(Resource, Options) -->
426 { resource_label(Resource, Label, Options)
427 },
428 html(Label).
429
437
438write_image_node(ImgAttrs, Attrs, Stream, _Options) :-
439 selectchk(src(Src), ImgAttrs, ImgAttrs1),
440 ( Src = icons(_)
441 -> absolute_file_name(Src, AbsFile, [access(read)]),
442 working_directory(CWD, CWD),
443 relative_file_name(AbsFile, CWD, File)
444 ; url_cache(Src, File, _MimeType)
445 ),
446 filter_attributes(Attrs, td, TDAttrs, _Attrs1),
447 html_current_option(dialect(Dialect)),
448 html_set_options([dialect(xhtml)]),
449 label_row(Attrs, Extra),
450 option(border(Border), Attrs),
451 phrase(html(table(border(Border),
452 [ tr(td(TDAttrs, img([src(File)|ImgAttrs1], [])))
453 | Extra
454 ])),
455 Tokens),
456 html_set_options([dialect(Dialect)]),
457 with_output_to(string(HTML), print_html(Tokens)),
458 write_attributes([html(HTML),shape(plaintext)], Stream).
459
460label_row(Attrs, Extra) :-
461 option(label(Label), Attrs), !,
462 Extra = [tr(td([align(center)], Label))].
463label_row(_, []).
464
465
473
474resource_label(Resource, Label, Options) :-
475 option(label_hook(Hook), Options),
476 !,
477 option(lang(Lang), Options, _),
478 option(max_label_length(MaxLen), Options, 25),
479 call(Hook, Resource, Lang, MaxLen, Label).
480resource_label(Resource, Label, Options) :-
481 option(lang(Lang), Options, _),
482 rdf_display_label(Resource, Lang, Text),
483 option(max_label_length(MaxLen), Options, 25),
484 truncate_atom(Text, MaxLen, Label).
485
486
487
494
495write_attributes([], Out) :- !,
496 format(Out, ' []').
497write_attributes(List, Out) :- !,
498 format(Out, ' [', []),
499 write_attributes_2(List, Out),
500 format(Out, ']', []).
501
502write_attributes_2([], _).
503write_attributes_2([H|T], Out) :-
504 ( string_attribute(H)
505 -> H =.. [Att, Value],
506 c_escape(Value, String),
507 format(Out, ' ~w="~s"', [Att, String])
508 ; html_attribute(H, Att)
509 -> arg(1, H, Value),
510 format(Out, ' ~w=<~s>', [Att, Value])
511 ; H =.. [Name, Value],
512 format(Out, ' ~w=~w', [Name, Value])
513 ),
514 write_attributes_2(T, Out).
515
516
517string_attribute(label(_)).
518string_attribute(url(_)).
519string_attribute(href(_)).
520string_attribute(id(_)).
521string_attribute('URL'(_)).
522string_attribute(fillcolor(_)).
523string_attribute(style(_)).
524
525html_attribute(html(_), label).
526
527
528c_escape(Atom, String) :-
529 atom_codes(Atom, Codes),
530 phrase(cstring(Codes), String).
531
534
535filter_attributes([], _, [], []).
536filter_attributes([H|T], E, ForE, Rest) :-
537 ( H =.. [Name,Value],
538 gv_attr(Name, E, Type),
539 is_of_type(Type, Value)
540 -> ForE = [H|R],
541 filter_attributes(T, E, R, Rest)
542 ; Rest = [H|R],
543 filter_attributes(T, E, ForE, R)
544 ).
545
552
553gv_attr(align, table, oneof([center,left,right])).
554gv_attr(bgcolor, table, atom).
555gv_attr(border, table, atom).
556gv_attr(cellborder, table, atom).
557gv_attr(cellpadding, table, atom).
558gv_attr(cellspacing, table, atom).
559gv_attr(color, table, atom).
560gv_attr(fixedsize, table, boolean).
561gv_attr(height, table, atom).
562gv_attr(href, table, atom).
563gv_attr(port, table, atom).
564gv_attr(target, table, atom).
565gv_attr(title, table, atom).
566gv_attr(tooltip, table, atom).
567gv_attr(valign, table, oneof([middle,bottom,top])).
568gv_attr(width, table, atom).
569
570gv_attr(align, td, oneof([center,left,right,text])).
571gv_attr(balign, td, oneof([center,left,right])).
572gv_attr(bgcolor, td, atom).
573gv_attr(border, td, atom).
574gv_attr(cellpadding, td, atom).
575gv_attr(cellspacing, td, atom).
576gv_attr(color, td, atom).
577gv_attr(colspan, td, integer).
578gv_attr(fixedsize, td, boolean).
579gv_attr(height, td, atom).
580gv_attr(href, td, atom).
581gv_attr(port, td, atom).
582gv_attr(rowspan, td, integer).
583gv_attr(target, td, atom).
584gv_attr(title, td, atom).
585gv_attr(tooltip, td, atom).
586gv_attr(valign, td, oneof([middle,bottom,top])).
587gv_attr(width, td, atom).
588
589gv_attr(color, font, atom).
590gv_attr(face, font, atom).
591gv_attr('point-size', font, integer).
592
593gv_attr(align, br, oneof([center,left,right])).
594
595gv_attr(scale, img, oneof([false,true,width,height,both])).
596gv_attr(src, img, atom).
597
598
604
605cstring([]) -->
606 [].
607cstring([H|T]) -->
608 ( cchar(H)
609 -> []
610 ; [H]
611 ),
612 cstring(T).
613
614cchar(0'") --> "\\\"".
615cchar(0'\n) --> "\\n".
616cchar(0'\t) --> "\\t".
617cchar(0'\b) --> "\\b".
618
619wrap_url(URL0, URL, Options) :-
620 option(wrap_url(Wrap), Options),
621 call(Wrap, URL0, URL), !.
622wrap_url(URL, URL, _).
623
624
628
629bag_shape(Members, Shape, Options) :-
630 option(bag_shape_hook(Hook), Options),
631 call(Hook, Members, Shape), !.
632bag_shape(_, [], _).
633
645
646shape(Resource, Shape, Options) :-
647 option(shape_hook(Hook), Options),
648 call(Hook, Resource, Shape), !.
649shape(Resource, Shape, _Options) :-
650 findall(Style, gv_style(Resource, Style), Shape),
651 debug(gv, '~p: shape = ~q', [Resource, Shape]).
652
653gv_style(R, Style) :-
654 rdfs_individual_of(R, Class),
655 gv_class_style(Class, Style).
656
657gv_class_style(Class, Style) :-
658 rdf_has(Class, graphviz:styleParameter, literal(V), P),
659 rdf_has(P, rdfs:label, literal(A)),
660 Style =.. [A,V].
661
662
663 666
669
670:- http_handler(root('graphviz/cache/url/'), cached_image_in_svg, [prefix]).
671:- http_handler(root('graphviz/'), local_image_in_svg, [prefix]).
672
679
680cached_image_in_svg(Request) :-
681 memberchk(path_info(PathInfo), Request),
682 atom_concat('cache/url/', PathInfo, File),
683 url_cached(URL, file(File)),
684 url_cached(URL, mime_type(MimeType)),
685 http_reply_file(File,
686 [ mime_type(MimeType),
687 unsafe(true)
688 ],
689 Request).
690
691local_image_in_svg(Request) :-
692 memberchk(path_info(PathInfo), Request),
693 file_base_name(PathInfo, ImageFile),
694 http_reply_file(icons(ImageFile), [], Request).
695
696
697
698 701
702:- rdf_meta
703 exclude_triple(r,r,o).
704
705exclude_triple(rdf(S,P,O)) :-
706 exclude_triple(S,P,O).
707
708exclude_triple(_,rdf:type,C) :-
709 rdf_has(C, graphviz:hideType, literal(type(xsd:boolean, true))).