36
37:- module(rdf_graphviz,
38 [ gviz_write_rdf/3 39 ]). 40:- use_module(library(semweb/rdf_db)). 41:- use_module(library(semweb/rdfs)). 42:- use_module(library(http/http_dispatch)). 43:- use_module(library(http/http_path)). 44:- use_module(library(http/html_write)). 45:- use_module(library(http/url_cache)). 46:- use_module(library(assoc)). 47:- use_module(library(option)). 48:- use_module(library(gensym)). 49:- use_module(library(lists)). 50:- use_module(library(apply)). 51:- use_module(library(ugraphs)). 52:- use_module(library(semweb/rdf_label)). 53
54:- rdf_register_ns(graphviz, 'http://www.graphviz.org/'). 55
64
133
134:- meta_predicate gviz_write_rdf(+,+,:). 135:- rdf_meta gviz_write_rdf(+,t,t). 136
137gviz_write_rdf(Stream, Graph0, Options0) :-
138 exclude(exclude_triple, Graph0, Graph1),
139 meta_options(is_meta, Options0, Options),
140 debug(edge(hook), 'Options = ~p', [Options]),
141 format(Stream, 'digraph G~n{ ', []),
142 option(graph_attributes(Attrs), Options, []),
143 write_graph_attributes(Attrs, Stream),
144 smash_graph(Graph1, Graph2, Options),
145 combine_bags(Graph2, Triples, Bags, Options),
146 gv_write_edges(Triples, Done, Stream,
147 [ graph(Graph0)
148 | Options
149 ]),
150 assoc_to_list(Done, Nodes),
151 gv_write_nodes(Nodes, Stream,
152 [ bag_assoc(Bags),
153 graph(Graph0)
154 | Options
155 ]),
156 format(Stream, '~n}~n', []).
157
158is_meta(wrap_url).
159is_meta(shape_hook).
160is_meta(edge_hook).
161is_meta(bag_shape_hook).
162is_meta(label_hook).
163
167
168write_graph_attributes([], _).
169write_graph_attributes([H|T], Out) :-
170 write_graph_attribute(H, Out),
171 write_graph_attributes(T, Out).
172
173write_graph_attribute(size(W,H), Out) :-
174 !,
175 format(Out, ' size="~w,~w";~n', [W, H]).
176write_graph_attribute(AttVal, Out) :-
177 AttVal =.. [Name, Value],
178 format(Out, ' ~w="~w";~n', [Name, Value]).
179
180
186
187combine_bags(Graph, Graph, Bags, Options) :-
188 option(bags(graph), Options),
189 !,
190 empty_assoc(Bags).
191combine_bags(Graph, Triples, Bags, _Options) :-
192 empty_assoc(Bags0),
193 find_bags(Graph, Graph1, Bags0, Bags1),
194 collect_bags(Graph1, Triples, Bags1, Bags).
195
196:- rdf_meta find_bags(t, -, +, -). 197
198find_bags([], [], Bags, Bags).
199find_bags([rdf(S,rdf:type,rdf:'Bag')|Graph], Triples, Bags0, Bags) :-
200 !,
201 put_assoc(S, Bags0, [], Bags1),
202 find_bags(Graph, Triples, Bags1, Bags).
203find_bags([H|T0], [H|T], Bags0, Bags) :-
204 find_bags(T0, T, Bags0, Bags).
205
206collect_bags([], [], Bags, Bags).
207collect_bags([rdf(S,P,O)|Graph], Triples, Bags0, Bags) :-
208 bagid_property(P, _),
209 get_assoc(S, Bags0, L, Bags1, [O|L]),
210 !,
211 collect_bags(Graph, Triples, Bags1, Bags).
212collect_bags([H|T0], [H|T], Bags0, Bags) :-
213 collect_bags(T0, T, Bags0, Bags).
214
215
220
221bagid_property(P, I) :-
222 atom(P),
223 !,
224 string_concat('_:', N, P),
225 number_string(I, N),
226 integer(I), I >= 0.
227bagid_property(P, I) :-
228 atom_concat('_:', I, P).
229
233
234smash_graph(GraphIn, GraphOut, Options) :-
235 option(smash(Props), Options, []),
236 !,
237 smash_graph_(Props, GraphIn, GraphOut).
238
239smash_graph_([], Graph, Graph).
240smash_graph_([H|T], Graph0, Graph) :-
241 smash_on_property(H, Graph0, Graph1),
242 smash_graph_(T, Graph1, Graph).
243
247
248smash_on_property(P, GraphIn, GraphOut) :-
249 smash_edges(GraphIn, P, Edges, Rest),
250 vertices_edges_to_ugraph([], Edges, Graph),
251 partition_ugraph(Graph, VerticeSets),
252 make_eq_bags(VerticeSets, VerticeBags, MapAssoc),
253 maplist(smash_triple(MapAssoc), Rest, Mapped),
254 append(Mapped, VerticeBags, GraphOut).
255
256smash_edges([], _, [], []).
257smash_edges([rdf(S,P,O)|T0], P, [S-O,O-S|T], Rest) :-
258 !,
259 smash_edges(T0, P, T, Rest).
260smash_edges([H|T0], P, Edges, [H|T]) :-
261 smash_edges(T0, P, Edges, T).
262
263partition_ugraph([], []) :- !.
264partition_ugraph(G0, [Vs0|Vs]) :-
265 G0 = [V-_|_],
266 reachable(V, G0, Vs0),
267 del_vertices(G0, Vs0, G1),
268 partition_ugraph(G1, Vs).
269
270make_eq_bags(Vertices, Bags, MapAssoc) :-
271 make_eq_bags(Vertices, 1, Bags, Mapping),
272 list_to_assoc(Mapping, MapAssoc).
273
274:- rdf_meta make_eq_bags(+, +, t, -). 275
276make_eq_bags([], _, [], []).
277make_eq_bags([Vs|T0], I, [rdf(BagId, rdf:type, rdf:'Bag')|Bags], Mapping) :-
278 atom_concat('_:sameAs', I, BagId),
279 make_eq_bag(Vs, 1, BagId, Bags, BagsT),
280 make_mapping(Vs, BagId, Mapping, MappingT),
281 I2 is I + 1,
282 make_eq_bags(T0, I2, BagsT, MappingT).
283
284make_eq_bag([], _, _, Triples, Triples).
285make_eq_bag([H|T], I, BagId, [rdf(BagId, P, H)|Triples0], Triples) :-
286 bagid_property(P, I),
287 I2 is I + 1,
288 make_eq_bag(T, I2, BagId, Triples0, Triples).
289
290make_mapping([], _, Mapping, Mapping).
291make_mapping([H|T], BagId, [H-BagId|Mapping0], Mapping) :-
292 make_mapping(T, BagId, Mapping0, Mapping).
293
294smash_triple(Mapping, rdf(S0,P,O0), rdf(S,P,O)) :-
295 smash(Mapping, S0, S),
296 smash(Mapping, O0, O).
297
298smash(Assoc, R0, R) :-
299 get_assoc(R0, Assoc, R),
300 !.
301smash(_, R, R).
302
303
310
311gv_write_edges(Graph, Done, Stream, Options) :-
312 empty_assoc(Done0),
313 gv_write_edges(Graph, Done0, Done, Stream, Options).
314
315gv_write_edges([], Done, Done, _, _).
316gv_write_edges([Triple|T], Done0, Done, Stream, Options) :-
317 write_edge(Triple, Done0, Done1, Stream, Options),
318 gv_write_edges(T, Done1, Done, Stream, Options).
319
320write_edge(rdf(S,P,O), Done0, Done2, Stream, Options) :-
321 format(Stream, ' ', []),
322 write_node_id(S, Done0, Done1, Stream),
323 write(Stream, ' -> '),
324 write_node_id(O, Done1, Done2, Stream),
325 edge_attributes(rdf(S,P,O), Attributes0, Options),
326 ( option(label(Label), Attributes0)
327 -> Attributes = Attributes0
328 ; resource_label(P, Label, Options),
329 Attributes = [label(Label)|Attributes0]
330 ),
331 ( option(edge_links(true), Options, true)
332 -> wrap_url(P, URL, Options),
333 target_option([href(URL), label(Label)|Attributes], Attrs, Options),
334 write_attributes(Attrs, Stream)
335 ; write_attributes(Attributes, Stream)
336 ),
337 nl(Stream).
338
339write_node_id(S, Done, Done, Stream) :-
340 get_assoc(S, Done, Id),
341 !,
342 write(Stream, Id).
343write_node_id(S, Done0, Done, Stream) :-
344 gensym(n, Id),
345 put_assoc(S, Done0, Id, Done),
346 write(Stream, Id).
347
352
353gv_write_nodes([], _, _).
354gv_write_nodes([RDF-ID|T], Stream, Options) :-
355 format(Stream, '~w ', [ID]),
356 write_node_attributes(RDF, Stream, Options),
357 write(Stream, ';\n '),
358 gv_write_nodes(T, Stream, Options).
359
364
365write_node_attributes(R, Stream, Options) :-
366 rdf_is_resource(R),
367 option(bag_assoc(Bags), Options),
368 get_assoc(R, Bags, Members),
369 !,
370 Members = [First|_],
371 shape(First, MemberShape0, Options),
372 bag_shape(Members, BagShape0, Options),
373 exclude(no_bag_option, MemberShape0, MemberShape),
374 option(bags(merge(BagShape1, Max0)), Options,
375 merge([ shape(box),
376 style('rounded,filled,bold'),
377 fillcolor('#ffff80')
378 ], 5)),
379 select_option(max(Max), BagShape0, BagShape2, Max0),
380 partition(label_option, BagShape2, LabelOptions0, BagShape2a),
381 merge_options(BagShape1, MemberShape, BagShape3),
382 merge_options(BagShape2a, BagShape3, BagShape),
383 merge_options(LabelOptions0, Options, LabelOptions),
384 bag_label(Members, Max, Label, LabelOptions),
385 write_attributes([html(Label)|BagShape], Stream).
386write_node_attributes(R, Stream, Options) :-
387 rdf_is_resource(R),
388 !,
389 shape(R, Shape, Options),
390 wrap_url(R, URL, Options),
391 resource_label(R, Label, Options),
392 target_option([href(URL), label(Label)|Shape], Attrs, Options),
393 ( select(img(IMGOptions), Attrs, RAttrs),
394 catch(write_image_node(IMGOptions, RAttrs, Stream, Options),
395 error(existence_error(url,URL2),Context),
396 ( print_message(warning,
397 error(existence_error(url,URL2),Context)),
398 fail))
399 -> true
400 ; delete(Attrs, img(_), RAttrs),
401 write_attributes(RAttrs, Stream)
402 ).
403write_node_attributes(Lit, Stream, Options) :-
404 shape(Lit, Shape, Options),
405 option(max_label_length(MaxLen), Options, 25),
406 literal_text(Lit, Text),
407 truncate_atom(Text, MaxLen, Summary0),
408 ( ( option(display_lang(true), Options, true),
409 Lit = literal(lang(Lang, _)))
410 -> atomic_list_concat([Summary0, '@', Lang], Summary)
411 ; Summary = Summary0
412 ),
413 write_attributes([label(Summary)|Shape], Stream).
414
415target_option(Attrs0, Attrs, Options) :-
416 option(target(Target), Options),
417 !,
418 Attrs = [target(Target)|Attrs0].
419target_option(Attrs, Attrs, _).
420
421no_bag_option(img(_)).
422no_bag_option(width(_)).
423no_bag_option(height(_)).
424no_bag_option(cellpadding(_)).
425no_bag_option(fixedsize(_)).
426no_bag_option(label(_)).
427no_bag_option(border(_)).
428
429label_option(max_label_length(_)).
430
438
439bag_label(Members, Max, Label, Options) :-
440 length(Members, Len),
441 phrase(html(table([ border(0) ],
442 \html_bag_label(Members, 1, Max, Len, Options))),
443 Tokens),
444 with_output_to(string(Label), print_html(Tokens)).
445
446html_bag_label([], _, _, _, _) --> !.
447html_bag_label(_, I, Max, Len, _Options) -->
448 { I > Max },
449 !,
450 html(tr(td([align(right), cellpadding(5)],
451 font(face('Helvetica:style=Italic'), '... showing ~D of ~D'-[Max, Len])))).
452html_bag_label([H|T], I, Max, Len, Options) -->
453 { ( atom(H)
454 -> wrap_url(H, URL, Options),
455 target_option([href(URL)], Atts, Options)
456 ; Atts=[]
457 )
458 },
459 html(tr(td([align(left)|Atts], \html_resource_label(H, Options)))),
460 { I2 is I + 1 },
461 html_bag_label(T, I2, Max, Len, Options).
462
463html_resource_label(Resource, Options) -->
464 { resource_label(Resource, Label, Options)
465 },
466 html(Label).
467
475
476write_image_node(ImgAttrs, Attrs, Stream, _Options) :-
477 selectchk(src(Src), ImgAttrs, ImgAttrs1),
478 ( Src = icons(_)
479 -> absolute_file_name(Src, AbsFile, [access(read)]),
480 working_directory(CWD, CWD),
481 relative_file_name(AbsFile, CWD, File)
482 ; url_cache(Src, File, _MimeType)
483 ),
484 filter_attributes(Attrs, td, TDAttrs, _Attrs1),
485 html_current_option(dialect(Dialect)),
486 html_set_options([dialect(xhtml)]),
487 label_row(Attrs, Extra),
488 option(border(Border), Attrs),
489 phrase(html(table(border(Border),
490 [ tr(td(TDAttrs, img([src(File)|ImgAttrs1], [])))
491 | Extra
492 ])),
493 Tokens),
494 html_set_options([dialect(Dialect)]),
495 with_output_to(string(HTML), print_html(Tokens)),
496 write_attributes([html(HTML),shape(plaintext)], Stream).
497
498label_row(Attrs, Extra) :-
499 option(label(Label), Attrs),
500 !,
501 Extra = [tr(td([align(center)], Label))].
502label_row(_, []).
503
504
512
513resource_label(Resource, Label, Options) :-
514 option(label_hook(Hook), Options),
515 option(lang(Lang), Options, _),
516 option(max_label_length(MaxLen), Options, 25),
517 call(Hook, Resource, Lang, MaxLen, Label),
518 !.
519resource_label(Resource, Label, Options) :-
520 option(lang(Lang), Options, _),
521 rdf_display_label(Resource, Lang, Text),
522 option(max_label_length(MaxLen), Options, 25),
523 truncate_atom(Text, MaxLen, Label).
524
525
526
533
534write_attributes([], Out) :-
535 !,
536 format(Out, ' []').
537write_attributes(List, Out) :-
538 !,
539 format(Out, ' [', []),
540 write_attributes_2(List, Out),
541 format(Out, ']', []).
542
543write_attributes_2([], _).
544write_attributes_2([H|T], Out) :-
545 ( string_attribute(H)
546 -> H =.. [Att, Value],
547 c_escape(Value, String),
548 format(Out, ' ~w="~s"', [Att, String])
549 ; html_attribute(H, Att)
550 -> arg(1, H, Value),
551 format(Out, ' ~w=<~s>', [Att, Value])
552 ; H =.. [Name, Value],
553 format(Out, ' ~w=~w', [Name, Value])
554 ),
555 write_attributes_2(T, Out).
556
557
558string_attribute(label(_)).
559string_attribute(url(_)).
560string_attribute(href(_)).
561string_attribute(id(_)).
562string_attribute('URL'(_)).
563string_attribute(fillcolor(_)).
564string_attribute(tooltip(_)).
565string_attribute(style(_)).
566
567html_attribute(html(_), label).
568
569
570c_escape(Atom, String) :-
571 atom_codes(Atom, Codes),
572 phrase(cstring(Codes), String).
573
576
577filter_attributes([], _, [], []).
578filter_attributes([H|T], E, ForE, Rest) :-
579 ( H =.. [Name,Value],
580 gv_attr(Name, E, Type),
581 is_of_type(Type, Value)
582 -> ForE = [H|R],
583 filter_attributes(T, E, R, Rest)
584 ; Rest = [H|R],
585 filter_attributes(T, E, ForE, R)
586 ).
587
594
595gv_attr(align, table, oneof([center,left,right])).
596gv_attr(bgcolor, table, atom).
597gv_attr(border, table, atom).
598gv_attr(cellborder, table, atom).
599gv_attr(cellpadding, table, atom).
600gv_attr(cellspacing, table, atom).
601gv_attr(color, table, atom).
602gv_attr(fixedsize, table, boolean).
603gv_attr(height, table, atom).
604gv_attr(href, table, atom).
605gv_attr(port, table, atom).
606gv_attr(target, table, atom).
607gv_attr(title, table, atom).
608gv_attr(tooltip, table, atom).
609gv_attr(valign, table, oneof([middle,bottom,top])).
610gv_attr(width, table, atom).
611
612gv_attr(align, td, oneof([center,left,right,text])).
613gv_attr(balign, td, oneof([center,left,right])).
614gv_attr(bgcolor, td, atom).
615gv_attr(border, td, atom).
616gv_attr(cellpadding, td, atom).
617gv_attr(cellspacing, td, atom).
618gv_attr(color, td, atom).
619gv_attr(colspan, td, integer).
620gv_attr(fixedsize, td, boolean).
621gv_attr(height, td, atom).
622gv_attr(href, td, atom).
623gv_attr(port, td, atom).
624gv_attr(rowspan, td, integer).
625gv_attr(target, td, atom).
626gv_attr(title, td, atom).
627gv_attr(tooltip, td, atom).
628gv_attr(valign, td, oneof([middle,bottom,top])).
629gv_attr(width, td, atom).
630
631gv_attr(color, font, atom).
632gv_attr(face, font, atom).
633gv_attr('point-size', font, integer).
634
635gv_attr(align, br, oneof([center,left,right])).
636
637gv_attr(scale, img, oneof([false,true,width,height,both])).
638gv_attr(src, img, atom).
639
640
646
647cstring([]) -->
648 [].
649cstring([H|T]) -->
650 ( cchar(H)
651 -> []
652 ; [H]
653 ),
654 cstring(T).
655
656cchar(0'") --> "\\\"".
657cchar(0'\n) --> "\\n".
658cchar(0'\t) --> "\\t".
659cchar(0'\b) --> "\\b".
660
661wrap_url(URL0, URL, Options) :-
662 option(wrap_url(Wrap), Options),
663 call(Wrap, URL0, URL),
664 !.
665wrap_url(URL, URL, _).
666
667
671
672bag_shape(Members, Shape, Options) :-
673 option(bag_shape_hook(Hook), Options),
674 call(Hook, Members, Shape),
675 !.
676bag_shape(_, [], _).
677
689
690shape(Resource, Shape, Options) :-
691 option(shape_hook(Hook), Options),
692 call(Hook, Resource, Shape, Options),
693 !.
694shape(Resource, Shape, _Options) :-
695 findall(Style, gv_style(Resource, Style), Shape),
696 debug(gv, '~p: shape = ~q', [Resource, Shape]).
697
698gv_style(R, Style) :-
699 rdfs_individual_of(R, Class),
700 gv_class_style(Class, Style).
701
702gv_class_style(Class, Style) :-
703 rdf_has(Class, graphviz:styleParameter, literal(V), P),
704 rdf_has(P, rdfs:label, literal(A)),
705 Style =.. [A,V].
706
707
711
712edge_attributes(Predicate, Attributes, Options) :-
713 option(edge_hook(Hook), Options),
714 debug(edge(hook), 'Hook = ~p', [Hook]),
715 call(Hook, Predicate, Attributes, Options),
716 !.
717edge_attributes(_, [], _).
718
719
720 723
726
727:- http_handler(root('graphviz/cache/url/'), cached_image_in_svg, [prefix]). 728:- http_handler(root('graphviz/'), local_image_in_svg, [prefix]). 729
736
737cached_image_in_svg(Request) :-
738 memberchk(path_info(PathInfo), Request),
739 atom_concat('cache/url/', PathInfo, File),
740 url_cached(URL, file(File)),
741 url_cached(URL, mime_type(MimeType)),
742 http_reply_file(File,
743 [ mime_type(MimeType),
744 unsafe(true)
745 ],
746 Request).
747
748local_image_in_svg(Request) :-
749 memberchk(path_info(PathInfo), Request),
750 file_base_name(PathInfo, ImageFile),
751 http_reply_file(icons(ImageFile), [], Request).
752
753
754
755 758
759:- rdf_meta
760 exclude_triple(r,r,o). 761
762exclude_triple(rdf(S,P,O)) :-
763 exclude_triple(S,P,O).
764
765exclude_triple(_,rdf:type,C) :-
766 rdf_has(C, graphviz:hideType, literal(type(xsd:boolean, true)))