View source with formatted comments or as raw
   1/*  Part of ClioPatria SeRQL and SPARQL server
   2
   3    Author:        Jan Wielemaker
   4    E-mail:        J.Wielemaker@cs.vu.nl
   5    WWW:           http://www.swi-prolog.org
   6    Copyright (C): 2009-2015, VU University Amsterdam
   7
   8    This program is free software; you can redistribute it and/or
   9    modify it under the terms of the GNU General Public License
  10    as published by the Free Software Foundation; either version 2
  11    of the License, or (at your option) any later version.
  12
  13    This program is distributed in the hope that it will be useful,
  14    but WITHOUT ANY WARRANTY; without even the implied warranty of
  15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16    GNU General Public License for more details.
  17
  18    You should have received a copy of the GNU General Public
  19    License along with this library; if not, write to the Free Software
  20    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  21
  22    As a special exception, if you link this library with other files,
  23    compiled with a Free Software compiler, to produce an executable, this
  24    library does not by itself cause the resulting executable to be covered
  25    by the GNU General Public License. This exception does not however
  26    invalidate any other reasons why the executable file might be covered by
  27    the GNU General Public License.
  28*/
  29
  30:- module(cpa_browse,
  31	  [ graph_info//1,		% +Graph
  32	    graph_as_resource//2,	% +Graph, +Options
  33	    graph_actions//1,		% +Graph
  34	    list_resource//2,		% +URI, +Options
  35	    context_graph//2		% +URI, +Options
  36	  ]).
  37:- use_module(library(http/http_dispatch)).
  38:- use_module(library(http/http_parameters)).
  39:- use_module(library(http/html_write)).
  40:- use_module(library(http/js_write)).
  41:- use_module(library(http/html_head)).
  42:- use_module(library(http/http_wrapper)).
  43:- use_module(library(http/yui_resources)).
  44:- use_module(library(http/http_path)).
  45:- use_module(library(http/cp_jquery)).
  46
  47:- use_module(library(semweb/rdf_db)).
  48:- use_module(library(semweb/rdfs)).
  49:- use_module(library(semweb/rdf_litindex)).
  50:- use_module(library(semweb/rdf_persistency)).
  51
  52:- use_module(library(aggregate)).
  53:- use_module(library(lists)).
  54:- use_module(library(pairs)).
  55:- use_module(library(debug)).
  56:- use_module(library(option)).
  57:- use_module(library(apply)).
  58
  59:- use_module(components(label)).
  60:- use_module(components(simple_search)).
  61:- use_module(components(graphviz)).
  62:- use_module(components(basics)).
  63:- use_module(api(lod_crawler)).
  64:- use_module(api(sesame)).
  65:- use_module(library(semweb/rdf_abstract)).
  66:- use_module(library(semweb/rdf_label)).
  67
  68:- use_module(user(user_db)).
  69
  70
  71/** <module> ClioPatria RDF data browser
  72
  73This module implements basic browsing of an  RDF repository. This is not
  74intended to be used as an end-user application, but for the developer to
  75gain insight in the data in the   RDF  store. That said, the distinction
  76between end-user and developer  can  be   rather  vague  if  we consider
  77`back-office'   applications.   To   a   certain   extend,   back-office
  78applications  are  considered  within  the  scope  of  this  module  and
  79therefore it provides several  hooks   and  defines several `components'
  80that allow back-office applications to reuse this infrastructure.
  81
  82@see	cliopatria(hooks) for available hooks.
  83*/
  84
  85
  86		 /*******************************
  87		 *	      PATHS		*
  88		 *******************************/
  89
  90:- http_handler(rdf_browser(.),
  91		http_404([index(list_graphs)]),
  92		[spawn(cliopatria), prefix]).
  93:- http_handler(rdf_browser(list_graphs),     list_graphs,     []).
  94:- http_handler(rdf_browser(list_graph),      list_graph,      []).
  95:- http_handler(rdf_browser(list_classes),    list_classes,    []).
  96:- http_handler(rdf_browser(list_instances),  list_instances,  []).
  97:- http_handler(rdf_browser(list_predicates), list_predicates, []).
  98:- http_handler(rdf_browser(list_predicate_resources),
  99					      list_predicate_resources, []).
 100:- http_handler(rdf_browser(list_resource),   list_resource,   []).
 101:- http_handler(rdf_browser(list_triples),    list_triples,    []).
 102:- http_handler(rdf_browser(list_triples_with_object),
 103					      list_triples_with_object,	[]).
 104:- http_handler(rdf_browser(list_triples_with_literal),
 105					      list_triples_with_literal, []).
 106
 107:- http_handler(rdf_browser(list_prefixes),   list_prefixes,   []).
 108:- http_handler(rdf_browser(search),          search,	       []).
 109:- http_handler(rdf_browser(multigraph_action), multigraph_action,
 110		[ time_limit(infinite) ]).
 111
 112
 113:- meta_predicate
 114	table_rows(3, +, ?, ?),
 115	table_rows_top_bottom(3, +, +, +, ?, ?),
 116	html_property_table(?, 0, ?, ?).
 117
 118%%	list_graphs(+Request)
 119%
 120%	Display a page holding a table with all RDF graphs.  The graphs
 121%	are sorted to the number of triples.
 122
 123list_graphs(_Request) :-
 124	findall(Count-Graph,
 125		(   rdf_graph(Graph),
 126		    graph_triples(Graph, Count)
 127		),
 128		Pairs),
 129	keysort(Pairs, Sorted),
 130	pairs_values(Sorted, UpCount),
 131	reverse(UpCount, DownCount),
 132	append(DownCount, [virtual(total)], Rows),
 133	reply_html_page(cliopatria(default),
 134			title('RDF Graphs'),
 135			[ h1('Named graphs in the RDF store'),
 136			  \warn_volatile,
 137			  \graph_table(Rows, [])
 138			]).
 139
 140:- if(current_predicate(rdf_persistency_property/1)).
 141warn_volatile -->
 142	{ rdf_persistency_property(access(read_only)), !,
 143	  rdf_persistency_property(directory(Dir))
 144	},
 145	html(div(class(msg_warning),
 146		 [ 'WARNING: The persistent store ', code(Dir), ' was loaded in ',
 147		   b('read-only'), ' mode.  All changes will be lost when ',
 148		   'the server is stopped.'
 149		 ])).
 150:- endif.
 151warn_volatile --> [].
 152
 153:- if((rdf_version(V),V>=30000)).
 154graph_triples(Graph, Count) :-
 155	rdf_statistics(triples_by_graph(Graph, Count)).
 156:- else.
 157graph_triples(Graph, Count) :-			% RDF-DB < 3.0
 158	rdf_statistics(triples_by_file(Graph, Count)).
 159:- endif.
 160
 161graph_table(Graphs, Options) -->
 162	{ option(top_max(TopMax), Options, 500),
 163	  option(top_max(BottomMax), Options, 500),
 164	  http_link_to_id(multigraph_action, [], Action),
 165	  graph_actions(Options, ActionOptions)
 166	},
 167	html_requires(css('rdf.css')),
 168	html(form([ action(Action),
 169		    class('graph-table')
 170		  ],
 171		  [ table(class(block),
 172			  [ \graph_table_header
 173			  | \table_rows_top_bottom(
 174				 graph_row(ActionOptions), Graphs,
 175				 TopMax, BottomMax)
 176			  ]),
 177		    \multigraph_actions(ActionOptions)
 178		  ])),
 179	mgraph_action_script.
 180
 181graph_table_header -->
 182	html(tr([ th('RDF Graph'),
 183		  th('Triples'),
 184		  th('Modified'),
 185		  th('Persistency')
 186		])).
 187
 188graph_row(_, virtual(total)) --> !,
 189	{ rdf_statistics(triples(Count))
 190	},
 191	html([ th(class(total), 'Total #triples:'),
 192	       \nc('~D', Count, [class(total)]),
 193	       td([],[]),  % Empty cell for persistency column
 194	       td([],[])   % Empty cell for modified column
 195	     ]).
 196graph_row(Options, Graph) -->
 197	{ graph_triples(Graph, Count)
 198
 199	},
 200	html([ td(\graph_link(Graph)),
 201	       \nc('~D', Count),
 202	       \modified(Graph),
 203	       td(style('text-align:center'), \persistency(Graph)),
 204	       \graph_checkbox(Graph, Options)
 205	     ]).
 206
 207modified(Graph) -->
 208	{ rdf_graph_property(Graph, source_last_modified(Time)),
 209	  format_time(string(Modified), '%+', Time), !
 210	},
 211	html(td([class('file-time')], Modified)).
 212modified(Graph) -->
 213	{ rdf_journal_file(Graph, File),
 214	  time_file(File, Time),
 215	  format_time(string(Modified), '%+', Time)
 216	},
 217	html(td([class('file-time')], Modified)).
 218modified(_Graph) -->
 219	html(td([class('file-time')], '')).
 220
 221graph_link(Graph) -->
 222	{ http_link_to_id(list_graph, [graph=Graph], URI)
 223	},
 224	html(a(href(URI), Graph)).
 225
 226persistency(Graph) -->
 227	{ rdf_graph_property(Graph, persistent(true)) }, !,
 228	snapshot(Graph),
 229	journal(Graph).
 230persistency(_) -->
 231	{ http_absolute_location(icons('volatile.png'), Img, [])
 232	},
 233	html(img([ class('in-text'),
 234		   title('Graph is not persistent'),
 235		   src(Img)
 236		 ])).
 237
 238snapshot(Graph) -->
 239	{ rdf_snapshot_file(Graph, _),
 240	  http_absolute_location(icons('snapshot.png'), Img, [])
 241	},
 242	html(img([ class('in-text'),
 243		   title('Graph has persistent snapshot'),
 244		   src(Img)
 245		 ])).
 246snapshot(_) --> [].
 247
 248journal(Graph) -->
 249	{ rdf_journal_file(Graph, _),
 250	  http_absolute_location(icons('journal.png'), Img, [])
 251	},
 252	html(img([ class('in-text'),
 253		   title('Graph has a journal'),
 254		   src(Img)
 255		 ])).
 256journal(_) --> [].
 257
 258%%	graph_actions(+Options0, -Options).
 259%%	multigraph_actions(+Options)
 260%
 261%	Deal with actions on multiple graphs.
 262
 263graph_actions(Options, [show_actions(true)|Options]) :-
 264	logged_on(User), !,
 265	catch(check_permission(User, write(_, unload(user))), _, fail), !.
 266graph_actions(Options, Options).
 267
 268graph_checkbox(Graph, Options) -->
 269	{ option(show_actions(true), Options) }, !,
 270	html(td(class('no-border'),
 271		input([type(checkbox),name(graph),value(Graph),
 272		       class('graph-select')]))).
 273graph_checkbox(_, _) --> [].
 274
 275multigraph_actions(Options) -->
 276	{ option(show_actions(true), Options), !,
 277	  findall(Action-Format,
 278		  clause(graph_action(Action,Format,_), _),
 279		  Pairs)
 280	},
 281	html([ ul([ class('multi-graph-actions')
 282		  ],
 283		  \li_graph_actions(Pairs))
 284	     ]).
 285multigraph_actions(_) --> [].
 286
 287li_graph_actions([]) --> [].
 288li_graph_actions([H|T]) --> li_graph_action(H), li_graph_actions(T).
 289
 290li_graph_action(Action-Format) -->
 291	{ atomic_list_concat([Pre,Post], '~w', Format) },
 292	html(li([ Pre,
 293		  input([ type(submit), name(action), value(Action) ]),
 294		  Post
 295		])).
 296
 297mgraph_action_script -->
 298	html_requires(jquery),
 299	js_script({|javascript||
 300function showActions(time) {
 301  if ( time === undefined ) time = 400;
 302  var val = [];
 303  $('.graph-table :checkbox:checked').each(function(i) {
 304    val[i] = $(this).val();
 305  });
 306  if ( val.length == 0 )
 307    $(".multi-graph-actions").hide(time);
 308  else
 309    $(".multi-graph-actions").show(time);
 310}
 311
 312$(function() {
 313  showActions(0);
 314  $(".graph-table .graph-select").on('click', showActions);
 315});
 316		   |}).
 317
 318%%	multigraph_action(Request)
 319%
 320%	HTTP Handler for user actions on multiple graphs.
 321
 322multigraph_action(Request) :-
 323	findall(Action, clause(graph_action(Action,_,_), _), Actions),
 324	http_parameters(Request,
 325			[ graph(Graphs, [list(atom)]),
 326			  action(Action, [oneof(Actions)])
 327			]),
 328	clause(graph_action(Action,Format,_), _),
 329	api_action(Request, multigraph_action(Action, Graphs), html,
 330		   Format-[Action]).
 331
 332multigraph_action(Action, Graphs) :-
 333	forall(member(Graph, Graphs),
 334	       ( print_message(informational,
 335			       format('Processing ~w ...', [Graph])),
 336		 graph_action(Action, _, Graph))).
 337
 338graph_action('Delete', '~w selected graphs', Graph) :-
 339	rdf_unload_graph(Graph).
 340graph_action(volatile, 'Make selected graphs ~w', Graph) :-
 341	rdf_persistency(Graph, false).
 342graph_action(persistent, 'Make selected graphs ~w', Graph) :-
 343	rdf_persistency(Graph, true).
 344graph_action('Merge journals', '~w for selected graphs', Graph) :-
 345	rdf_flush_journals([graph(Graph)]).
 346
 347
 348%%	list_graph(+Request)
 349%
 350%	HTTP handler that provides information   about an individual RDF
 351%	graph. The output is an HTML table.
 352
 353list_graph(Request) :-
 354	http_parameters(Request,
 355			[ graph(Graph,
 356				[description('Name of the graph to describe')])
 357			]),
 358	(   rdf_graph(Graph)
 359	->  true
 360	;   http_404([], Request)
 361	),
 362	reply_html_page(cliopatria(default),
 363			title('RDF Graph ~w'-[Graph]),
 364			[ h1('Summary information for graph "~w"'-[Graph]),
 365			  \simple_search_form([ id(ac_find_in_graph),
 366						filter(graph(Graph)),
 367						label('Search this graph')
 368					      ]),
 369			  \graph_info(Graph),
 370			  \graph_as_resource(Graph, []),
 371			  \graph_persistency(Graph),
 372			  \graph_actions(Graph),
 373			  \uri_info(Graph, Graph)
 374			]).
 375
 376%%	graph_info(+Graph)//
 377%
 378%	HTML component that shows  -statistical-   properties  about the
 379%	given named graph.
 380
 381graph_info(Graph) -->
 382	html_property_table(row(P,V),
 383			    graph_property(Graph,P,V)).
 384
 385:- dynamic
 386	graph_property_cache/3.
 387
 388graph_property(Graph, P, V) :-
 389	graph_property_cache(Graph, MD5, Pairs),
 390	rdf_md5(Graph, MD5), !,
 391	member(P0-V, Pairs),
 392	P =.. [P0,Graph].
 393graph_property(Graph, P, V) :-
 394	retractall(graph_property_cache(Graph, _, _)),
 395	findall(P-V, graph_property_nc(Graph, P, V), Pairs),
 396	rdf_md5(Graph, MD5),
 397	assert(graph_property_cache(Graph, MD5, Pairs)),
 398	member(P0-V, Pairs),
 399	P =.. [P0,Graph].
 400
 401graph_property_nc(Graph, source, Source) :-
 402	rdf_source(Graph, Source).
 403graph_property_nc(Graph, triples, int(Triples)) :-
 404	graph_triples(Graph, Triples).
 405graph_property_nc(Graph, predicate_count, int(Count)) :-
 406	aggregate_all(count, predicate_in_graph(Graph, _P), Count).
 407graph_property_nc(Graph, subject_count, int(Count)) :-
 408	aggregate_all(count, subject_in_graph(Graph, _P), Count).
 409graph_property_nc(Graph, bnode_count, int(Count)) :-
 410	aggregate_all(count, bnode_in_graph(Graph, _P), Count).
 411graph_property_nc(Graph, type_count, int(Count)) :-
 412	aggregate_all(count, type_in_graph(Graph, _P), Count).
 413
 414predicate_in_graph(Graph, P) :-
 415	rdf_current_predicate(P),
 416	once(rdf(_,P,_,Graph)).
 417
 418%%	subject_in_graph(+Graph, -Subject)
 419%
 420%	Generate the distinct subjects in a graph. There are two ways to
 421%	do this: first the subjects and then  whether they appear in the
 422%	graph or the other way around. At   least this has the advantage
 423%	that we get distinct subjects for free.
 424
 425subject_in_graph(Graph, S) :-
 426	graph_triples(Graph, Count),
 427	rdf_statistics(triples(Total)),
 428	Count * 10 > Total, !,		% Graph has more than 10% of triples
 429	rdf_subject(S),
 430	once(rdf(S, _, _, Graph)).
 431subject_in_graph(Graph, S) :-
 432	findall(S, rdf(S,_,_,Graph), List),
 433	sort(List, Subjects),
 434	member(S, Subjects).
 435
 436bnode_in_graph(Graph, S) :-
 437	graph_triples(Graph, Count),
 438	rdf_statistics(triples(Total)),
 439	Count * 10 > Total, !,
 440	rdf_subject(S),
 441	rdf_is_bnode(S),
 442	once(rdf(S, _, _, Graph)).
 443bnode_in_graph(Graph, S) :-
 444	findall(S, (rdf(S,_,_,Graph), rdf_is_bnode(S)), List),
 445	sort(List, Subjects),
 446	member(S, Subjects).
 447
 448
 449
 450%%	type_in_graph(+Graph, -Class)
 451%
 452%	Generate the unique types in Graph
 453
 454:- thread_local
 455	type_seen/1.
 456
 457type_in_graph(Graph, Class) :-
 458	call_cleanup(type_in_graph2(Graph, Class),
 459		     retractall(type_seen(_))).
 460
 461type_in_graph2(Graph, Class) :-
 462	subject_in_graph(Graph, S),
 463	(   rdf_has(S, rdf:type, Class)
 464	*-> true
 465	;   rdf_equal(Class, rdfs:'Resource')
 466	),
 467	(   type_seen(Class)
 468	->  fail
 469	;   assert(type_seen(Class))
 470	).
 471
 472
 473%%	graph_persistency(+Graph)//
 474%
 475%	Show information about the persistency of the graph
 476
 477graph_persistency(Graph) -->
 478	{ rdf_graph_property(Graph, persistent(true)),
 479	  (   rdf_journal_file(Graph, _)
 480	  ;   rdf_snapshot_file(Graph, _)
 481	  )
 482	}, !,
 483	html([ h1('Persistency information'),
 484	       table(class(block),
 485		     [ tr([ td(class('no-border'),[]),
 486			    th('File'), th('Size'),th('Modified'),
 487			    td(class('no-border'),[])
 488			  ]),
 489		       \graph_shapshot(Graph),
 490		       \graph_journal(Graph)
 491		     ])
 492	     ]).
 493graph_persistency(Graph) -->
 494	{ rdf_graph_property(Graph, persistent(true))
 495	}, !,
 496	html([ h1('Persistency information'),
 497	       p('The graph has no associated persistency files')
 498	     ]).
 499graph_persistency(_Graph) -->
 500	[].
 501
 502graph_shapshot(Graph) -->
 503	{ rdf_snapshot_file(Graph, File)
 504	},
 505	html(tr([ th(class('file-role'), 'Snapshot'),
 506		  \file_info(File)
 507		])).
 508graph_shapshot(_) --> [].
 509
 510
 511graph_journal(Graph) -->
 512	{ rdf_journal_file(Graph, File)
 513	},
 514	html(tr([ th(class('file-role'), 'Journal'),
 515		  \file_info(File),
 516		  \flush_journal_button(Graph)
 517		])).
 518graph_journal(_) --> [].
 519
 520flush_journal_button(Graph) -->
 521	{ http_link_to_id(flush_journal, [], HREF)
 522	},
 523	html(td(class('no-border'),
 524		form(action(HREF),
 525		     [ input([type(hidden), name(graph), value(Graph)]),
 526		       input([type(hidden), name(resultFormat), value(html)]),
 527		       input([type(submit), value('Merge journal')])
 528		     ]))).
 529
 530
 531file_info(File) -->
 532	{ size_file(File, Size),
 533	  time_file(File, Time),
 534	  format_time(string(Modified), '%+', Time)
 535	},
 536	html([ td(class('file-name'), File),
 537	       td(class('int'), \n(human, Size)),
 538	       td(class('file-time'), Modified)
 539	     ]).
 540
 541
 542%%	graph_actions(+Graph)// is det.
 543%
 544%	Provide a form for basic actions on the graph
 545
 546graph_actions(Graph) -->
 547	html([ h2('Actions'),
 548	       ul(class(graph_actions),
 549		  [ \li_export_graph(Graph, show),
 550		    \li_export_graph(Graph, download),
 551		    \li_schema_graph(Graph),
 552		    \li_delete_graph(Graph),
 553		    \li_persistent_graph(Graph)
 554		  ])
 555	     ]).
 556
 557li_delete_graph(Graph) -->
 558	{ logged_on(User),
 559	  catch(check_permission(User, write(_, unload(Graph))), _, fail), !,
 560	  http_link_to_id(unload_graph, [], Action)
 561	},
 562	html(li(form(action(Action),
 563		     [ input([type(hidden), name(graph), value(Graph)]),
 564		       input([type(hidden), name(resultFormat), value(html)]),
 565		       input([class(gaction), type(submit), value('Delete')]),
 566		       ' this graph'
 567		     ]))).
 568li_delete_graph(_) --> [].
 569
 570li_persistent_graph(Graph) -->
 571	{ logged_on(User),
 572	  catch(check_permission(User, write(_, persistent(Graph))), _, fail), !,
 573	  http_link_to_id(modify_persistency, [], Action),
 574	  (   rdf_graph_property(Graph, persistent(true))
 575	  ->  Op = (volatile),   Value = off
 576	  ;   Op = (persistent), Value = on
 577	  )
 578	}, !,
 579	html(li(form(action(Action),
 580		     [ input([type(hidden), name(graph), value(Graph)]),
 581		       input([type(hidden), name(resultFormat), value(html)]),
 582		       input([type(hidden), name(persistent), value(Value)]),
 583		       'Make this graph ',
 584		       input([class(gaction), type(submit), value(Op)])
 585		     ]))).
 586li_persistent_graph(_) --> [].
 587
 588li_schema_graph(Graph) -->
 589	{ http_link_to_id(export_graph_schema, [], Action),
 590	  download_options(show, Label, MimeType, Title)
 591	},
 592	html(li(form(action(Action),
 593		     [ input([type(hidden), name(graph), value(Graph)]),
 594		       input([type(hidden), name(mimetype), value(MimeType)]),
 595		       'Compute a schema for this graph and ',
 596		       input([class(saction), type(submit), value(Label),
 597			      title(Title)
 598			     ]),
 599		       ' the result as ',
 600		       \dl_format_menu
 601		     ]))).
 602
 603li_export_graph(Graph, How) -->
 604	{ http_link_to_id(export_graph, [], Action),
 605	  download_options(How, Label, MimeType, Title)
 606	},
 607	html(li(form(action(Action),
 608		     [ input([type(hidden), name(graph), value(Graph)]),
 609		       input([type(hidden), name(mimetype), value(MimeType)]),
 610		       input([class(gaction), type(submit), value(Label),
 611			      title(Title)
 612			     ]),
 613		       ' this graph as ',
 614		       \dl_format_menu
 615		     ]))).
 616
 617download_options(show,     'Show',     'text/plain',
 618		 'Returns graph with MIME-type text/plain, \n\c
 619		  so it will be displayed in your browser').
 620download_options(download, 'Download', default,
 621		 'Return graph with its RDF MIME-type, \n\c
 622		  so most browsers will save it').
 623
 624dl_format_menu -->
 625	html(select(name(format),
 626		    [ option([value(turtle),selected],  'Turtle'),
 627		      option([value(canonical_turtle)], 'Canonical Turtle'),
 628		      option([value(rdfxml)],           'RDF/XML')
 629		    ])).
 630
 631
 632%%	list_classes(+Request)
 633%
 634%	HTTP handler that lists all classes  of all subjects that appear
 635%	in the named graph. The  output  is   an  HTML  page holding all
 636%	referenced classes sorted by their label.
 637
 638list_classes(Request) :-
 639	http_parameters(Request,
 640			[ graph(Graph, [description('Name of the graph')])
 641			]),
 642	types_in_graph(Graph, Map),
 643	sort_pairs_by_label(Map, Sorted),
 644	reply_html_page(cliopatria(default),
 645			title('Classes in graph ~w'-[Graph]),
 646			[ h1(['Classes in graph ', \graph_link(Graph)]),
 647			  \class_table(Sorted, Graph, [])
 648			]).
 649
 650class_table(Pairs, Graph, Options) -->
 651	{ option(top_max(TopMax), Options, 500),
 652	  option(top_max(BottomMax), Options, 500)
 653	},
 654	html_requires(css('rdf.css')),
 655	html(table(class(block),
 656		   [ \class_table_header
 657		   | \table_rows_top_bottom(class_row(Graph), Pairs,
 658					    TopMax, BottomMax)
 659		   ])).
 660
 661class_table_header -->
 662	html(tr([ th('Class'),
 663		  th('#Instances')
 664		])).
 665
 666class_row(Graph, Class) -->
 667	{ atom(Class), !,
 668	  findall(I, rdf_has(I, rdf:type, Class, Graph), IL),
 669	  sort(IL, Classes),
 670	  length(Classes, InstanceCount)
 671	},
 672	class_row(Graph, Class-InstanceCount).
 673class_row(Graph, Class-InstanceCount) -->
 674	{ (   var(Graph)
 675	  ->  Params = [class(Class)]
 676	  ;   Params = [graph(Graph), class(Class)]
 677	  ),
 678	  http_link_to_id(list_instances, Params, ILink)
 679	},
 680	html([ td(\rdf_link(Class)),
 681	       td(class(int), a(href(ILink), InstanceCount))
 682	     ]).
 683
 684%%	types_in_graph(+Graph, -Map:list(Type-InstanceCount))
 685%
 686%	Generate a map of all types that appear in Graph with a count on
 687%	the number of instances.
 688
 689types_in_graph(Graph, Map) :-
 690	findall(S, subject_in_graph(Graph, S), Subjects),
 691	types(Subjects, Pairs),
 692	transpose_pairs(Pairs, TypeSubj),
 693	group_pairs_by_key(TypeSubj, TypeSubjs),
 694	maplist(instance_count, TypeSubjs, Map).
 695
 696types([], []).
 697types([S|T0], Types) :-
 698	call_det(type_of(S,C), Det), !,
 699	(   Det == true
 700	->  Types = [S-C|T],
 701	    types(T0, T)
 702	;   findall(C2, type_of(S,C2), Cs),
 703	    multi_class(Cs, S, Types, PT),
 704	    types(T0, PT)
 705	).
 706
 707multi_class([], _, Pairs, Pairs).
 708multi_class([H|T], S, [S-H|Pairs], PT) :-
 709	multi_class(T, S, Pairs, PT).
 710
 711
 712type_of(Subject, Type) :-
 713	(   rdf_has(Subject, rdf:type, Type)
 714	*-> true
 715	;   rdf_equal(Type, rdfs:'Resource')
 716	).
 717
 718:- meta_predicate
 719	call_det(0, -).
 720
 721call_det(G, Det) :-
 722	call(G),
 723	deterministic(Det).
 724
 725instance_count(Type-Instances, Type-Count) :-
 726	length(Instances, Count).
 727
 728%%	instance_in_graph(?Graph, ?Class, +Type,
 729%%			  -Subject, -PropertyCount) is nondet.
 730%
 731%	True of Subject is  an  instance   of  Class  with PropertyCount
 732%	properties provided from Graph.
 733
 734instance_in_graph(Graph, Class, any, S, C) :- !,
 735	instance_in_graph(Graph, Class, S, C).
 736instance_in_graph(Graph, Class, bnode, S, C) :- !,
 737	freeze(S, rdf_is_bnode(S)),
 738	instance_in_graph(Graph, Class, S, C).
 739
 740
 741instance_in_graph(Graph, Class, S, C) :-
 742	var(Class), !,
 743	subject_in_graph(Graph, S),
 744	property_count(Graph, S, C).
 745instance_in_graph(Graph, Class, S, C) :-
 746	rdf_equal(Class, rdfs:'Resource'), !,
 747	(   rdf_has(S, rdf:type, Class),
 748	    once(rdf(S, _, _, Graph))
 749	;   subject_in_graph(Graph, S),
 750	    \+ rdf_has(S, rdf:type, _)
 751	),
 752	property_count(Graph, S, C).
 753instance_in_graph(Graph, Class, S, C) :-
 754	rdf_has(S, rdf:type, Class),
 755	once(rdf(S, _, _, Graph)),
 756	property_count(Graph, S, C).
 757
 758property_count(Graph, S, Count) :-
 759	aggregate_all(count, rdf(S, _, _, Graph), Count).
 760
 761%%	graph_as_resource(+Graph, Options)// is det.
 762%
 763%	Show resource info for a graph if it is described.
 764
 765graph_as_resource(Graph, Options) -->
 766	{ (   rdf(Graph, _, _)
 767	  ;   rdf(_, Graph, _)
 768	  ;   rdf(_, _, Graph)
 769	  ), !
 770	},
 771	html([ h2([ 'Local view for "',
 772		    \location(Graph, _), '"'
 773		  ]),
 774	       \local_view(Graph, _, Options)
 775	     ]).
 776graph_as_resource(_, _) --> [].
 777
 778
 779		 /*******************************
 780		 *	  LIST INSTANCES	*
 781		 *******************************/
 782
 783%%	list_instances(+Request)
 784%
 785%	HTTP handler that lists instances that satisfy certain criteria.
 786
 787list_instances(Request) :-
 788	http_parameters(Request,
 789			[ class(Class,
 790				[ optional(true),
 791				  description('Limit to instances of this class')
 792				]),
 793			  graph(Graph,
 794				[ optional(true),
 795				  description('Limit to have at least \c
 796					       one property in graph')
 797				]),
 798			  type(Type,
 799			       [ oneof([any, bnode]),
 800				 default(any),
 801				 description('Any instance or only bnodes?')
 802			       ]),
 803			  sortBy(Sort,
 804				 [ oneof([label,properties]),
 805				   default(label),
 806				   description('How to sort the result-table')
 807				 ])
 808			]),
 809	findall(I-PC, instance_in_graph(Graph, Class, Type, I, PC), IPairs),
 810	sort_pairs_by_label(IPairs, TableByName),
 811	(   Sort == properties
 812	->  reverse(TableByName, RevTableByName),
 813	    transpose_pairs(RevTableByName, FPairsUp),
 814	    reverse(FPairsUp, FPairsDown),
 815	    flip_pairs(FPairsDown, Table)
 816	;   Table = TableByName
 817	),
 818
 819	reply_html_page(cliopatria(default),
 820			title(\instance_table_title(Graph, Class, Sort)),
 821			[ h1(\html_instance_table_title(Graph, Class, Sort)),
 822			  \instance_table(Table, [resource_format(nslabel)])
 823			]).
 824
 825instance_table_title(Graph, Class, Sort) -->
 826	{ var(Class) }, !,
 827	html('Instances in ~w sorted by ~w'-
 828	     [Graph, Sort]).
 829instance_table_title(Graph, Class, Sort) -->
 830	{ rdf_display_label(Class, Label) },
 831	html('Instances of ~w in ~w sorted by ~w'-
 832	     [Label, Graph, Sort]).
 833
 834html_instance_table_title(Graph, Class, Sort) -->
 835	html([ 'Instances',
 836	       \of_class(Class),
 837	       \in_graph(Graph),
 838	       \sorted_by(Sort)
 839	     ]).
 840
 841of_class(Class) -->
 842	{ var(Class) }, !.
 843of_class(Class) -->
 844	html([' of class ', \rdf_link(Class)]).
 845
 846in_graph(Graph) -->
 847	{ var(Graph) }, !.
 848in_graph(Graph) -->
 849	html([' in graph ', \graph_link(Graph)]).
 850
 851sorted_by(Sort) -->
 852	html(' sorted by ~w'-[Sort]).
 853
 854
 855instance_table(Pairs, Options) -->
 856	{ option(top_max(TopMax), Options, 500),
 857	  option(top_max(BottomMax), Options, 500)
 858	},
 859	html_requires(css('rdf.css')),
 860	html(table(class(block),
 861		   [ \instance_table_header
 862		   | \table_rows_top_bottom(instance_row(Options), Pairs,
 863					    TopMax, BottomMax)
 864		   ])).
 865
 866instance_table_header -->
 867	html(tr([ th('Instance'),
 868		  th('#Properties')
 869		])).
 870
 871instance_row(Options, R-C) -->
 872	html([ td(\rdf_link(R, Options)),
 873	       td(class(int), C)
 874	     ]).
 875
 876
 877		 /*******************************
 878		 *	     PREDICATES		*
 879		 *******************************/
 880
 881%%	list_predicates(+Request)
 882%
 883%	List all predicates used in graph, sorted by label.
 884
 885list_predicates(Request) :-
 886	http_parameters(Request,
 887			[ graph(Graph, [])
 888			]),
 889	findall(Pred, predicate_in_graph(Graph, Pred), Preds),
 890	sort_by_label(Preds, Sorted),
 891	reply_html_page(cliopatria(default),
 892			title('Predicates in graph ~w'-[Graph]),
 893			[ h1(['Predicates in graph ', \graph_link(Graph)]),
 894			  \predicate_table(Sorted, Graph, [])
 895			]).
 896
 897predicate_table(Preds, Graph, Options) -->
 898	{ option(top_max(TopMax), Options, 500),
 899	  option(bottom_max(BottomMax), Options, 500)
 900	},
 901	html_requires(css('rdf.css')),
 902	html(table(class(block),
 903		   [ \predicate_table_header
 904		   | \table_rows_top_bottom(predicate_row(Graph), Preds,
 905					    TopMax, BottomMax)
 906		   ])).
 907
 908predicate_table_header -->
 909	html(tr([ th('Predicate'),
 910		  th('#Triples'),
 911		  th('#Distinct subjects'),
 912		  th('#Distinct objects'),
 913		  th('Domain(s)'),
 914		  th('Range(s)')
 915		])).
 916
 917%%	predicate_row(?Graph, +Pred) is det.
 918
 919predicate_row(Graph, Pred) -->
 920	{ predicate_statistics(Graph, Pred, Triples,
 921			       Subjects, Objects, Doms, Ranges),
 922	  (   var(Graph)
 923	  ->  Params = [predicate(Pred)]
 924	  ;   Params = [graph(Graph), predicate(Pred)]
 925	  ),
 926	  http_link_to_id(list_triples,   Params, PLink)
 927	},
 928	html([ td(\rdf_link(Pred)),
 929	       td(class(int), a(href(PLink), Triples)),
 930	       \resources(Subjects, subject, Params, []),
 931	       \resources(Objects, object, Params, []),
 932	       \resources(Doms, domain, Params, []),
 933	       \resources(Ranges, range, Params, [])
 934	     ]).
 935
 936resources([], _, _, _) --> !,
 937	html(td(class(empty), -)).
 938resources([One], _, _, Options) --> !,
 939	html(td(\rdf_link(One, Options))).
 940resources(Many, What, Params, _) --> !,
 941	{ (   integer(Many)
 942	  ->  Count = Many
 943	  ;   length(Many, Count)
 944	  ),
 945	  http_link_to_id(list_predicate_resources, [side(What)|Params], Link)
 946	},
 947	html(td(class(int_c), a(href(Link), Count))).
 948
 949:- dynamic
 950	predicate_statistics_cache/8.
 951
 952predicate_statistics(Graph, P, C, Subjects, Objects, Domains, Ranges) :-
 953	var(Graph), !,
 954	predicate_statistics_(Graph, P, C, Subjects, Objects, Domains, Ranges).
 955predicate_statistics(Graph, P, C, Subjects, Objects, Domains, Ranges) :-
 956	rdf_md5(Graph, MD5),
 957	predicate_statistics_cache(MD5, Graph, P, C,
 958				   Subjects, Objects, Domains, Ranges), !.
 959predicate_statistics(Graph, P, C, Subjects, Objects, Domains, Ranges) :-
 960	rdf_md5(Graph, MD5),
 961	debug(rdf_browse, 'Recomputing pred stats for ~p in ~w, MD5=~w',
 962	      [P, Graph, MD5]),
 963	retractall(predicate_statistics_cache(MD5, Graph, P, _,
 964					      _, _, _, _)),
 965	predicate_statistics_(Graph, P, C, SubjectL, ObjectL, DomainL, RangeL),
 966	res_summary(SubjectL, Subjects),
 967	res_summary(ObjectL, Objects),
 968	res_summary(DomainL, Domains),
 969	res_summary(RangeL, Ranges),
 970	assertz(predicate_statistics_cache(MD5, Graph, P, C,
 971					   Subjects, Objects, Domains, Ranges)).
 972
 973
 974res_summary([], []) :- !.
 975res_summary([One], [One]) :- !.
 976res_summary(Many, Count) :-
 977	length(Many, Count).
 978
 979
 980predicate_statistics_(Graph, P, C, Subjects, Objects, Domains, Ranges) :-
 981	findall(S-O, rdf(S,P,O,Graph), Pairs),
 982	length(Pairs, C),
 983	pairs_keys_values(Pairs, Ss, Os),
 984	sort(Ss, Subjects),
 985	sort(Os, Objects),
 986	resources_types(Subjects, Graph, Domains),
 987	resources_types(Objects, Graph, Ranges).
 988
 989resources_types(URIs, Graph, Types) :-
 990	findall(T, resource_type_in(URIs, Graph, T), TList),
 991	sort(TList, Types).
 992
 993resource_type_in(List, Graph, T) :-
 994	member(URI, List),
 995	resource_type(URI, Graph, T).
 996
 997%%	resource_type(+URI, +Graph, -Type) is det.
 998
 999resource_type(URI, Graph, T) :-
1000	(   URI = literal(Lit)
1001	->  (   Lit = type(T, _)
1002	    ->	true
1003	    ;	rdf_equal(T, rdfs:'Literal')
1004	    )
1005	;   rdf(URI, rdf:type, T, Graph)
1006	*-> true
1007	;   rdf_equal(T, rdfs:'Resource')
1008	).
1009
1010
1011		 /*******************************
1012		 *	  LIST RESOURCES	*
1013		 *******************************/
1014
1015%%	list_predicate_resources(+Request)
1016%
1017%	List resources related to a predicate.   The  _side_ argument is
1018%	one of:
1019%
1020%	    * subject
1021%	    Display all subject values for the predicate
1022%	    * object
1023%	    Display all object values for the predicate
1024%	    * domain
1025%	    Display the types of all subject values
1026%	    * range
1027%	    Display the types of all object values.
1028%
1029%	If the _skosmap_ attribute is =true=,   an extra column is added
1030%	that shows SKOS concepts that match   literals.  This only makes
1031%	sense if _side_ = =object= and (some) objects are literals.
1032
1033list_predicate_resources(Request) :-
1034	http_parameters(Request,
1035			[ graph(Graph,
1036				[ optional(true),
1037				  description('Limit search to this graph')
1038				]),
1039			  predicate(Pred,
1040				    [ description('Predicate to list')
1041				    ]),
1042			  side(Which,
1043			       [ oneof([subject,object,domain,range]),
1044				 description('Relation to the predicate (see docs)')
1045			       ]),
1046			  sortBy(Sort,
1047				 [ oneof([label,frequency]),
1048				   default(frequency),
1049				   description('How to sort results')
1050				 ]),
1051			  skosmap(SkosMap,
1052				  [ boolean,
1053				    optional(true),
1054				    description('Show SKOS concepts for literals')
1055				  ])
1056			]),
1057	do_skos(SkosMap, Which, Pred),
1058	findall(R, predicate_resource(Graph, Pred, Which, R), Set),
1059	term_frequency_list(Set, FPairs),
1060	sort_pairs_by_label(FPairs, TableByName),
1061	(   Sort == frequency
1062	->  reverse(TableByName, RevTableByName),
1063	    transpose_pairs(RevTableByName, FPairsUp),
1064	    reverse(FPairsUp, FPairsDown),
1065	    flip_pairs(FPairsDown, Table)
1066	;   Table = TableByName
1067	),
1068
1069	pred_resource_options(Pred, Which, Options),
1070
1071	reply_html_page(cliopatria(default),
1072			title(\resource_table_title(Graph, Pred, Which, Sort)),
1073			[ h1(\html_resource_table_title(Graph, Pred, Which,
1074							Sort, SkosMap)),
1075			  \resource_frequency_table(Table,
1076						    [ skosmap(SkosMap),
1077						      predicate(Pred),
1078						      side(Which),
1079						      sort(Sort)
1080						    | Options
1081						    ])
1082			]).
1083
1084pred_resource_options(_, domain, [label('Class')]) :- !.
1085pred_resource_options(_, range, [label('Class')]) :- !.
1086pred_resource_options(_, _, []).
1087
1088do_skos(SkosMap, _, _) :-
1089	nonvar(SkosMap), !.
1090do_skos(SkosMap, object, Pred) :-
1091	\+ rdf(_, Pred, literal(_)), !,
1092	SkosMap = false.
1093do_skos(SkosMap, object, _) :-
1094	rdfs_individual_of(_, skos:'ConceptScheme'), !,
1095	SkosMap = true.
1096do_skos(false, _, _).
1097
1098
1099resource_table_title(Graph, Pred, Which, Sort) -->
1100	{ rdf_display_label(Pred, PLabel)
1101	},
1102	html('Distinct ~ws for ~w in ~w sorted by ~w'-
1103	     [Which, PLabel, Graph, Sort]
1104	     ).
1105
1106html_resource_table_title(Graph, Pred, Which, Sort, SkosMap) -->
1107	html([ 'Distinct ~ws'-[Which],
1108	       \for_predicate(Pred),
1109	       \in_graph(Graph),
1110	       \sorted_by(Sort),
1111	       \showing_skosmap(SkosMap)
1112	     ]).
1113
1114for_predicate(Pred) -->
1115	{ var(Pred) }, !.
1116for_predicate(Pred) -->
1117	html([' for predicate ', \rdf_link(Pred, [])]).
1118
1119showing_skosmap(true) --> !,
1120	html(' with mapping to SKOS').
1121showing_skosmap(_) --> [].
1122
1123resource_frequency_table(Pairs, Options) -->
1124	{ option(top_max(TopMax), Options, 500),
1125	  option(top_max(BottomMax), Options, 500),
1126	  option(predicate(Pred), Options, _),
1127	  option(side(Side), Options)
1128	},
1129	html_requires(css('rdf.css')),
1130	html(table(class(block),
1131		   [ \resource_table_header(Options)
1132		   | \table_rows_top_bottom(resource_row(Pred, Side, Options), Pairs,
1133					    TopMax, BottomMax)
1134		   ])).
1135
1136resource_table_header(Options) -->
1137	{ option(label(Label), Options, 'Resource'),
1138	  (   option(sort(Sort), Options)
1139	  ->  (   Sort == frequency
1140	      ->  A1 = [],
1141		  A2 = [class(sorted)]
1142	      ;	  A1 = [class(sorted)],
1143		  A2 = []
1144	      )
1145	  ;   A1 = [],
1146	      A2 = []
1147	  )
1148	},
1149	html(tr([ th(A1, Label),
1150		  th(A2, 'Count'),
1151		  \skosmap_head(Options)
1152		])).
1153
1154skosmap_head(Options) -->
1155	{ option(skosmap(true), Options) }, !,
1156	html(th('SKOS mapping')).
1157skosmap_head(_) --> [].
1158
1159resource_row(Pred, object, Options, R-C) --> !,
1160	{ object_param(R, Param),
1161	  http_link_to_id(list_triples_with_object,
1162	       [ p(Pred),
1163		 Param
1164	       ], HREF)
1165	},
1166	html([ td(\rdf_link(R, Options)),
1167	       td(class(int), a(href(HREF), C)),
1168	       \skosmap(R, Options)
1169	     ]).
1170resource_row(Pred, Side, Options, R-C) -->
1171	{ domain_range_parameter(Side, R, Param), !,
1172	  http_link_to_id(list_triples,
1173	       [ predicate(Pred),
1174		 Param
1175	       ], HREF)
1176	},
1177	html([ td(\rdf_link(R, Options)),
1178	       td(class(int), a(href(HREF), C)),
1179	       \skosmap(R, Options)
1180	     ]).
1181resource_row(_, _, Options, R-C) -->
1182	html([ td(\rdf_link(R, Options)),
1183	       td(class(int), C),
1184	       \skosmap(R, Options)
1185	     ]).
1186
1187object_param(R, r=R) :-
1188	atom(R), !.
1189object_param(L, l=A) :-
1190	term_to_atom(L, A).
1191
1192domain_range_parameter(domain, R, domain(R)).
1193domain_range_parameter(range,  R, range(R)).
1194
1195%%	skosmap(+Literal, +Options)//
1196%
1197%	Component that emits a =td= cell with links to SKOS concepts
1198%	that are labeled Literal.
1199
1200skosmap(Literal, Options) -->
1201	{ Literal = literal(_),
1202	  option(skosmap(true), Options),
1203	  findall(Concept-Scheme, skos_find(Literal, Concept, Scheme), Pairs),
1204	  Pairs \== [],
1205	  sort_pairs_by_label(Pairs, Sorted)
1206	},
1207	html(td(\skos_references(Sorted))).
1208skosmap(_, _) --> [].
1209
1210skos_find(Literal, Concept, Scheme) :-
1211	rdf_has(Concept, skos:prefLabel, Literal),
1212	rdf_has(Concept, skos:inScheme, Scheme).
1213
1214skos_references([]) --> [].
1215skos_references([H|T]) -->
1216	skos_reference(H),
1217	(   { T == [] }
1218	->  []
1219	;   html('; '),
1220	    skos_references(T)
1221	).
1222
1223skos_reference(Concept-Scheme) -->
1224	html([\rdf_link(Concept), ' in ', \rdf_link(Scheme)]).
1225
1226
1227flip_pairs([], []).
1228flip_pairs([Key-Val|Pairs], [Val-Key|Flipped]) :-
1229	flip_pairs(Pairs, Flipped).
1230
1231predicate_resource(Graph, Pred, subject, R) :- !,
1232	rdf(R, Pred, _, Graph).
1233predicate_resource(Graph, Pred, object, R) :- !,
1234	rdf(_, Pred, R, Graph).
1235predicate_resource(Graph, Pred, domain, D) :- !,
1236	rdf(R, Pred, _, Graph),
1237	rdf(R, rdf:type, D, Graph).
1238predicate_resource(Graph, Pred, range, R) :-
1239	rdf(_, Pred, O, Graph),
1240	resource_type(O, Graph, R).
1241
1242%%	term_frequency_list(+Terms, -TermFrequencyPairs)
1243%
1244%	TermFrequencyPairs is a list if  pairs Value-Count of equivalent
1245%	term in Terms.  Equivalence is determined using ==/2.  The terms
1246%	themselves are sorted on the standard order of terms.
1247
1248term_frequency_list(Resources, Pairs) :-
1249	msort(Resources, Sorted),
1250	fpairs(Sorted, Pairs).
1251
1252fpairs([], []).
1253fpairs([H|T0], [H-C|T]) :-
1254	pick_same(T0, T1, H, 1, C),
1255	fpairs(T1, T).
1256
1257pick_same([H1|T0], L, H, F0, F) :-
1258	H == H1, !,
1259	F1 is F0 + 1,
1260	pick_same(T0, L, H, F1, F).
1261pick_same(L, L, _, F, F).
1262
1263
1264		 /*******************************
1265		 *    LIST A SINGLE RESOURCE	*
1266		 *******************************/
1267
1268%%	list_resource(+Request)
1269%
1270%	HTTP handler that lists the property table for a single resource
1271%	(=local view)
1272%
1273%	@see	The functionality of this handler is also available as
1274%		an embedable component through list_resource//2.
1275
1276list_resource(Request) :-
1277	http_parameters(Request,
1278			[ r(URI,
1279			    [ description('URI to describe')]),
1280			  sorted(Sorted,
1281				 [ oneof([default,none]),
1282				   default(default),
1283				   description('How to sort properties')
1284				 ]),
1285			  graph(Graph,
1286				[ optional(true),
1287				  description('Limit to properties from graph')
1288				]),
1289			  raw(Raw,
1290			      [ default(false),
1291				boolean,
1292				description('If true, omit application hook')
1293			      ])
1294			]),
1295	rdf_display_label(URI, Label),
1296	reply_html_page(cliopatria(default),
1297			title('Resource ~w'-[Label]),
1298			\list_resource(URI,
1299				       [ graph(Graph),
1300					 sorted(Sorted),
1301					 raw(Raw),
1302					 resource_format(nslabel)
1303				       ])).
1304
1305%%	list_resource(+URI, +Options)// is det.
1306%
1307%	Component that emits the `local view'   for  URI. The local view
1308%	shows the basic properties  of  URI,   the  context  in which is
1309%	appears and the graphs from which the information is extracted.
1310%	Options is one of:
1311%
1312%	    * graph(Graph)
1313%	    Limit properties in the table to the given graph
1314%	    * sorted(Sorted)
1315%	    One of =default= or =none=.
1316%
1317%	Calls the hook cliopatria:list_resource//2.  For compatibility
1318%	reasons, it also tries the hook cliopatria:list_resource//1.
1319%
1320%	@see	list_resource/1 is the corresponding HTTP handler.  The
1321%		component rdf_link//1 creates a link to list_resource/1.
1322
1323:- multifile
1324	cliopatria:list_resource//1.
1325
1326list_resource(URI, Options) -->
1327	{ \+ option(raw(true), Options) },
1328	(   cliopatria:list_resource(URI, Options)
1329	->  []
1330	;   cliopatria:list_resource(URI) % deprecated
1331	).
1332list_resource(URI, Options) -->
1333	{ option(graph(Graph), Options, _)
1334	},
1335	html([ h1([ 'Local view for "',
1336		    \location(URI, Graph), '"'
1337		  ]),
1338	       \define_prefix(URI),
1339	       \local_view(URI, Graph, Options),
1340	       p(\as_object(URI, Graph)),
1341	       p(\as_graph(URI)),
1342	       \uri_info(URI, Graph)
1343	     ]).
1344
1345%%	define_prefix(+URI)//
1346%
1347%	Allow defining a new prefix if the  resource is not covered by a
1348%	prefix.
1349
1350define_prefix(URI) -->
1351	{ rdf_global_id(_Prefix:_Local, URI) }, !.
1352define_prefix(URI) -->
1353	{ iri_xml_namespace(URI, Namespace, LocalName),
1354	  LocalName \== '',
1355	  http_link_to_id(add_prefix, [], Action)
1356	},
1357	html(form(action(Action),
1358		  ['No prefix for ', a(href(Namespace),Namespace), '. ',
1359		   \hidden(uri, Namespace),
1360		   input([name(prefix), size(8),
1361			  title('Short unique abbreviation')
1362			 ]),
1363		   input([type(submit), value('Add prefix')])
1364		  ])).
1365define_prefix(_) -->			% Not a suitable URI.  Warn?
1366	[].
1367
1368
1369%%	location(+URI, ?Graph) is det.
1370%
1371%	Show the URI. If the URI is a blank node, show its context using
1372%	Turtle notation.
1373
1374location(URI, _Graph) -->
1375	{ rdf_is_bnode(URI), !,
1376	  findall(Path, path_to_non_bnode(URI, Path), Paths),
1377	  sort_by_length(Paths, PathsByLen),
1378	  partition(starts_bnode, PathsByLen, StartsBNode, StartsReal),
1379	  (   StartsReal = [Path|_]
1380	  ->  true
1381	  ;   last(StartsBNode, Path)
1382	  )
1383	},
1384	bnode_location(Path).
1385location(URI, _) -->
1386	html(URI).
1387
1388bnode_location([P-URI]) --> !,
1389	html([ '[', \rdf_link(P, []), ' ',
1390	            \rdf_link(URI),
1391	       ']'
1392	     ]).
1393bnode_location([P-URI|More]) --> !,
1394	html([ '[', div(class(bnode_attr),
1395			[ div(\rdf_link(P, [])),
1396			  div(\rdf_link(URI))
1397			]), ' ',
1398	       \bnode_location(More),
1399	       ']'
1400	     ]).
1401bnode_location([URI|More]) --> !,
1402	rdf_link(URI),
1403	html(' '),
1404	bnode_location(More).
1405bnode_location([]) -->
1406	[].
1407
1408path_to_non_bnode(URI, Path) :-
1409	path_to_non_bnode_rev(URI, [URI], RevPath),
1410	reverse(RevPath, Path).
1411
1412path_to_non_bnode_rev(URI, Seen, [P-URI|Path]) :-
1413	(   rdf_is_bnode(URI),
1414	    rdf(S, P, URI),
1415	    \+ memberchk(S, Seen)
1416	*-> path_to_non_bnode_rev(S, [S|Seen], Path)
1417	;   fail
1418	).
1419path_to_non_bnode_rev(URI, _, [URI]).
1420
1421starts_bnode([URI|_]) :-
1422	rdf_is_bnode(URI).
1423
1424sort_by_length(ListOfLists, ByLen) :-
1425	map_list_to_pairs(length, ListOfLists, Pairs),
1426	keysort(Pairs, Sorted),
1427	pairs_values(Sorted, ByLen).
1428
1429%%	as_graph(+URI) is det.
1430%
1431%	Show the places where URI is used as a named graph
1432
1433as_graph(URI) --> { \+ rdf_graph(URI) }, !.
1434as_graph(URI) -->
1435	 html([ 'This resource is also a ',
1436		a([href(location_by_id(list_graph)+'?graph='+encode(URI))],
1437		  'named graph'),
1438		'.']).
1439
1440
1441%%	as_object(+URI, +Graph) is det.
1442%
1443%	Show the places where URI is used as an object.
1444
1445as_object(URI, Graph) -->
1446	{ findall(S-P, rdf(S,P,URI,Graph), Pairs),
1447	  sort(Pairs, Unique)
1448	},
1449	as_object_locations(Unique, URI, Graph).
1450
1451as_object_locations([], _URI, _) --> !,
1452	html([ 'The resource does not appear as an object' ]).
1453as_object_locations([S-P], URI, _) --> !,
1454	html([ 'The resource appears as object in one triple:',
1455	       blockquote(class(triple),
1456			  [ '{ ',
1457			    \rdf_link(S), ', ',
1458			    \rdf_link(P, []), ', ',
1459			    \rdf_link(URI),
1460			    ' }'
1461			  ])
1462	     ]).
1463as_object_locations(List, URI, Graph) --> !,
1464	{ length(List, Len),
1465	  (   var(Graph)
1466	  ->  Extra = []
1467	  ;   Extra = [graph=Graph]
1468	  ),
1469	  http_link_to_id(list_triples_with_object, [r=URI|Extra], Link)
1470	},
1471	html([ 'The resource appears as object in ',
1472	       a(href(Link), [Len, ' triples'])
1473	     ]).
1474
1475%%	local_view(+URI, ?Graph, +Options) is det.
1476%
1477%	Show the local-view table for URI.  If Graph is given, only show
1478%	triples from the given graph.  Options processed:
1479%
1480%	    * top_max(+Count)
1481%	    * bottom_max(+Count)
1482%	    * sorted(+How)
1483%	    Defines the order of the predicates. One of =none=
1484%	    (database order) or =default=
1485%	    * show_graph(+Bool)
1486%
1487%	In addition, Options are passed to rdf_link//2.
1488
1489local_view(URI, Graph, Options) -->
1490	{ option(top_max(TopMax), Options, 500),
1491	  option(bottom_max(BottomMax), Options, 500),
1492	  po_pairs(URI, Graph, Pairs, Options),
1493	  lview_graphs(URI, Graph, Graphs)
1494	},
1495	(   { Pairs \== []
1496	    }
1497	->  html_requires(css('rdf.css')),
1498	    html(table(class(block),
1499		       [ \lview_header(Options)
1500		       | \table_rows_top_bottom(lview_row(Options, URI, Graphs),
1501						Pairs,
1502						TopMax, BottomMax)
1503		       ])),
1504	    graph_footnotes(Graphs, Options)
1505	;   { lod_uri_graph(URI, LODGraph),
1506	      rdf_graph(LODGraph)
1507	    }
1508	->  html(p([ 'No triples for ', \show_link(URI), '. ',
1509		     'Linked Data was loaded into ', \graph_link(LODGraph),
1510		     '.'
1511		   ]))
1512	;   { sane_uri(URI) }
1513	->  { http_link_to_id(lod_crawl, [], FetchURL),
1514	      http_current_request(Request),
1515	      memberchk(request_uri(Here), Request)
1516	    },
1517	    html(form(action(FetchURL),
1518		      [ \hidden(r, URI),
1519			\hidden(return_to, Here),
1520			'No triples for ', \show_link(URI),
1521			'.  Would you like to ',
1522			input([ type(submit),
1523				value('Query the Linked Data cloud')
1524			      ]),
1525			'?'
1526		      ]))
1527	;   html_requires(css('rdf.css')),
1528	    html(p([ 'No triples for ', \show_link(URI),
1529		     ' (unknown URI scheme).']))
1530	).
1531
1532show_link(URI) -->
1533	{ sane_uri(URI) }, !,
1534	html(a(href(URI), 'this URI')).
1535show_link(URI) -->
1536	html(span(class('insecure-uri'), URI)).
1537
1538sane_uri(URI) :-
1539	uri_components(URI, Components),
1540	uri_data(scheme, Components, Scheme),
1541	valid_scheme(Scheme),
1542	uri_data(authority, Components, Authority),
1543	nonvar(Authority).
1544
1545valid_scheme(http).
1546valid_scheme(https).
1547valid_scheme(ftp).
1548valid_scheme(ftps).
1549
1550lview_header(Options) -->
1551	{ option(sorted(Sorted), Options, default),
1552	  alt_sorted(Sorted, Alt),
1553	  http_current_request(Request),
1554	  http_reload_with_parameters(Request, [sorted(Alt)], HREF)
1555	},
1556	html(tr([ th('Predicate'),
1557		  th(['Value (sorted: ', a(href(HREF), Sorted), ')'])
1558		])).
1559
1560alt_sorted(default, none).
1561alt_sorted(none, default).
1562
1563
1564lview_row(Options, S, Graphs, P-OList) -->
1565	html([ td(class(predicate), \rdf_link(P, Options)),
1566	       td(class(object), \object_list(OList, S, P, Graphs, Options, 1))
1567	     ]).
1568
1569object_list([], _, _, _, _, _) --> [].
1570object_list([H|T], S, P, Graphs, Options, Row) -->
1571	{ NextRow is Row + 1,
1572	  obj_class(Row, Class)
1573	},
1574	html(div(class(Class),
1575		 [ \rdf_link(H, Options),
1576		   \graph_marks(S, P, H, Graphs)
1577		 ])),
1578	object_list(T, S, P, Graphs, Options, NextRow).
1579
1580obj_class(N, Class) :-
1581	(   N mod 2 =:= 0
1582	->  Class = even
1583	;   Class = odd
1584	).
1585
1586graph_marks(_,_,_,[_]) --> !.
1587graph_marks(S,P,O,Graphs) -->
1588	html(sup(class(graph), \graphs(S,P,O,Graphs))).
1589
1590graphs(S, P, O, Graphs) -->
1591	{ findall(G, rdf(S,P,O,G:_), GL) },
1592	graphs(GL, Graphs).
1593
1594graphs([], _) --> [].
1595graphs([H|T], Graphs) -->
1596	{ nth1(N, Graphs, H) -> true },
1597	html(N),
1598	(   { T == [] }
1599	->  []
1600	;   html(','),
1601	    graphs(T, Graphs)
1602	).
1603
1604%%	graph_footnotes(+GraphList, +Options)//
1605%
1606%	Describe footnote marks in the local   view  table that indicate
1607%	the origin of triples.
1608
1609graph_footnotes([], _Options) --> !.
1610graph_footnotes([Graph], _Options) --> !,
1611	html(p(class('graphs-used'),
1612	       [ 'All properties reside in the graph ',
1613		 \graph_link(Graph)
1614	       ])).
1615graph_footnotes(Graphs, Options) -->
1616	html(p(class('graphs-used'),
1617	       'Named graphs describing this resource:')),
1618	graph_footnotes(Graphs, 1, Options).
1619
1620graph_footnotes([], _, _) --> [].
1621graph_footnotes([H|T], N, Options) -->
1622	html(div(class('graph-fn'),
1623		 [ sup(class(graph), N),
1624		   \graph_link(H)
1625		 ])),
1626	{ N2 is N + 1 },
1627	graph_footnotes(T, N2, Options).
1628
1629%%	lview_graphs(+Subject, ?Graph, -Graphs) is det.
1630
1631lview_graphs(_Subject, Graph, Graphs) :-
1632	nonvar(Graph), !,
1633	Graphs = [Graph].
1634lview_graphs(Subject, Graph, Graphs) :-
1635	findall(Graph, rdf(Subject, _, _, Graph:_), Graphs0),
1636	sort(Graphs0, Graphs).
1637
1638%%	po_pairs(+Subject, ?Graph, -Pairs, +Options) is det.
1639%
1640%	Pairs is a  list  of  P-ObjectList   for  the  S,P,O  triples on
1641%	Subject. The list is normally sorted  by predicate as defined by
1642%	p_order/2 below.
1643
1644po_pairs(S, Graph, Pairs, Options) :-
1645	option(sorted(none), Options), !,
1646	findall(P-[O], rdf(S,P,O,Graph), Pairs).
1647po_pairs(S, Graph, Pairs, _Options) :-
1648	var(Graph), !,
1649	findall(P-OL,
1650		setof(O, rdf(S,P,O), OL),
1651		Pairs0),
1652	sort_po(Pairs0, Pairs).
1653po_pairs(S, Graph, Pairs, _Options) :-
1654	findall(P-OL,
1655		setof(O, rdf(S,P,O,Graph), OL),
1656		Pairs0),
1657	sort_po(Pairs0, Pairs).
1658
1659%%	sort_po(+Pairs, -Sorted) is det.
1660%
1661%	Sort a list of P-ValueList. This is   used  to keep the dominant
1662%	rdf, rdfs, skos, etc. properties in a   fixed order at the start
1663%	of the table.
1664
1665sort_po(Pairs, Sorted) :-
1666	map_list_to_pairs(po_key, Pairs, Keyed),
1667	keysort(Keyed, KeySorted),
1668	exclude(=(0-_), KeySorted, Remaining),
1669	pairs_values(Remaining, Sorted).
1670
1671po_key(P-_, Key) :-
1672	p_order(P, Key), !.
1673po_key(P-_, Key) :-
1674	label_sort_key(P, Key).
1675
1676%%	p_order(+P, -SortKey) is semidet.
1677%
1678%	SortKey is the key used for sorting the predicate P.
1679%
1680%	@tbd	Make this hookable.
1681
1682:- rdf_meta
1683	p_order(r,?).
1684
1685p_order(P, Order) :-
1686	cliopatria:predicate_order(P, Order), !.
1687p_order(P, 100) :-
1688	label_property(P), !.
1689p_order(P, 110) :-
1690	rdfs_subproperty_of(P, skos:altLabel), !.
1691p_order(rdf:type,	  210).
1692p_order(rdfs:subClassOf,  220).
1693p_order(rdfs:domain,	  230).
1694p_order(rdfs:range,	  240).
1695p_order(rdfs:comment,	  310).
1696p_order(rdfs:isDefinedBy, 320).
1697
1698
1699%%	uri_info(+URI, +Graph)// is det.
1700%
1701%	Display additional info and actions about   a URI in the context
1702%	of the given graph.
1703
1704uri_info(URI, Graph) -->
1705	uri_class_info(URI, Graph),
1706	uri_predicate_info(URI, Graph),
1707	html(h2('Context graph')),
1708	context_graph(URI, []).
1709
1710uri_class_info(URI, Graph) -->
1711	{ rdf_current_predicate(URI)
1712	}, !,
1713	html(h2('Predicate statistics')),
1714	predicate_table([URI], Graph, []).
1715uri_class_info(_,_) --> [].
1716
1717uri_predicate_info(URI, Graph) -->
1718	{ \+ \+ rdf(_, rdf:type, URI, Graph)
1719	}, !,
1720	html(h2('Class statistics')),
1721	class_table([URI], Graph, []).
1722uri_predicate_info(_, _) --> [].
1723
1724
1725%%	context_graph(+URI, +Options)// is det.
1726%
1727%	Show graph with  the  context  of   URI.  Options  is  passed to
1728%	cliopatria:context_graph/3  and  cliopatria:node_shape/3.    Two
1729%	options have special meaning:
1730%
1731%	    * style(?Style)
1732%	    If this option is not specified, it is passed as a variable.
1733%	    It can be tested or filled by cliopatria:context_graph/3 and
1734%	    subsequently used by cliopatria:node_shape/3.
1735%
1736%	    * start(+URI)
1737%	    Passed to cliopatria:node_shape/3 to indicate the origin of
1738%	    the context graph.
1739
1740context_graph(URI, Options) -->
1741	{ merge_options(Options, [style(_)], GraphOption),
1742	  rdf_equal(owl:sameAs, SameAs)
1743	},
1744	html([ \graphviz_graph(context_graph(URI, GraphOption),
1745			       [ object_attributes([width('100%')]),
1746				 wrap_url(resource_link),
1747				 graph_attributes([ rankdir('RL')
1748						  ]),
1749				 shape_hook(shape(URI, GraphOption)),
1750				 bag_shape_hook(bag_shape(GraphOption)),
1751				 smash([SameAs])
1752			       ])
1753	     ]).
1754
1755:- public
1756	shape/4,
1757	bag_shape/3.
1758
1759%%	shape(+Start, +Options, +URI, -Shape) is semidet.
1760%
1761%	Specify GraphViz shape for URI. This   predicate  calls the hook
1762%	cliopatria:node_shape/3.
1763
1764shape(Start, Options, URI, Shape) :-
1765	cliopatria:node_shape(URI, Shape, [start(Start)|Options]), !.
1766shape(Start, _Options, Start,
1767      [ shape(tripleoctagon),style(filled),fillcolor('#ff85fd'),id(start) ]).
1768
1769%%	bag_shape(+Options, +Members, -Shape) is semidet.
1770%
1771%	Compute properties for a bag
1772
1773bag_shape(Options, Members, Shape) :-
1774	cliopatria:bag_shape(Members, Shape, Options), !.
1775bag_shape(_, _, []).
1776
1777
1778
1779%%	context_graph(+URI, -Triples, +Options) is det.
1780%
1781%	Triples is a graph  that  describes   the  environment  of  URI.
1782%	Currently, the environment is defined as:
1783%
1784%	    * skos:related properties (using 1 step)
1785%	    * Transitive properties (using upto 3 steps).
1786%
1787%	This predicate can be hooked using cliopatria:context_graph/2.
1788
1789context_graph(URI, Options, RDF) :-
1790	cliopatria:context_graph(URI, RDF, Options), !.
1791context_graph(URI, _Options, RDF) :-		% Compatibility
1792	cliopatria:context_graph(URI, RDF), !.
1793context_graph(URI, _, RDF) :-
1794	findall(T, context_triple(URI, T), RDF0),
1795	sort(RDF0, RDF1),
1796	minimise_graph(RDF1, RDF2),		% remove inverse/symmetric/...
1797	bagify_graph(RDF2, RDF3, Bags, []),	% Create bags of similar resources
1798	append(RDF3, Bags, RDF).
1799
1800:- rdf_meta
1801	transitive_context(r),
1802	context(r).
1803
1804context_triple(URI, Triple) :-
1805	transitive_context(CP),
1806	parents(URI, CP, Triples, [URI], 3),
1807	member(Triple, Triples).
1808context_triple(URI, Triple) :-
1809	cliopatria:context_predicate(URI, R),
1810	rdf_has(URI, R, O, P),
1811	normalize_triple(rdf(URI, P, O), Triple).
1812context_triple(URI, Triple) :-
1813	context(R),
1814	rdf_has(URI, R, O, P),
1815	normalize_triple(rdf(URI, P, O), Triple).
1816context_triple(URI, Triple) :-
1817	context(R),
1818	rdf_has(S, R, URI, P),
1819	normalize_triple(rdf(S, P, URI), Triple).
1820
1821normalize_triple(rdf(S, inverse_of(P0), O),
1822		 rdf(O, P, S)) :- !,
1823	rdf_predicate_property(P0, inverse_of(P)).
1824normalize_triple(RDF, RDF).
1825
1826
1827
1828parents(URI, Up, [Triple|T], Visited, MaxD) :-
1829	succ(MaxD2, MaxD),
1830	rdf_has(URI, Up, Parent, P),
1831	normalize_triple(rdf(URI, P, Parent), Triple),
1832	\+ memberchk(Parent, Visited),
1833	parents(Parent, Up, T, [Parent|Visited], MaxD2).
1834parents(_, _, [], _, _).
1835
1836transitive_context(owl:sameAs).
1837transitive_context(rdfs:subClassOf).
1838transitive_context(rdfs:subPropertyOf).
1839transitive_context(skos:broader).
1840transitive_context(P) :-
1841	rdfs_individual_of(P, owl:'TransitiveProperty'),
1842	rdf_predicate_property(P, rdfs_subject_branch_factor(BF)),
1843	BF < 2.0.
1844
1845context(skos:related).
1846context(skos:mappingRelation).
1847
1848%%	list_triples(+Request)
1849%
1850%	List  triples  for  a  given    predicate.  The  triple-set  can
1851%	optionally be filtered on the graph, type of the subject or type
1852%	of the object.
1853
1854list_triples(Request) :-
1855	http_parameters(Request,
1856			[ predicate(P,
1857				    [ optional(true),
1858				      description('Limit triples to this pred')]),
1859			  graph(Graph, [ optional(true),
1860					 description('Limit triples to this graph')
1861				       ]),
1862			  domain(Dom,  [ optional(true),
1863					 description('Restrict to subjects of this class')
1864				       ]),
1865			  range(Range, [ optional(true),
1866					 description('Restrict to objects of this class')
1867				       ])
1868			]),
1869	(   atom(Dom)
1870	->  findall(rdf(S,P,O), rdf_in_domain(S,P,O,Dom,Graph), Triples0)
1871	;   atom(Range)
1872	->  findall(rdf(S,P,O), rdf_in_range(S,P,O,Range,Graph), Triples0)
1873	;   findall(rdf(S,P,O), rdf(S,P,O,Graph), Triples0)
1874	),
1875	sort(Triples0, Triples),
1876	sort_triples_by_label(Triples, Sorted),
1877	length(Sorted, Count),
1878	(   var(P)
1879	->  Title = 'Triples in graph ~w'-[Graph]
1880	;   rdf_display_label(P, PLabel),
1881	    Title = 'Triples for ~w in graph ~w'-[PLabel, Graph]
1882	),
1883	reply_html_page(cliopatria(default),
1884			title(Title),
1885			[ h1(\triple_header(Count, P, Dom, Range, Graph)),
1886			  \triple_table(Sorted, P, [resource_format(nslabel)])
1887			]).
1888
1889rdf_in_domain(S,P,O,Dom,Graph) :-
1890	rdf(S, P, O, Graph),
1891	rdf_has(S, rdf:type, Dom).
1892
1893rdf_in_range(S,P,O,Lit,Graph) :-
1894	rdf_equal(rdfs:'Literal', Lit), !,
1895	O = literal(_),
1896	rdf(S, P, O, Graph).
1897rdf_in_range(S,P,O,Rng,Graph) :-
1898	rdf_equal(rdfs:'Resource', Rng), !,
1899	rdf(S, P, O, Graph),
1900	atom(O).
1901rdf_in_range(S,P,O,Rng,Graph) :-
1902	rdf(S, P, O, Graph),
1903	rdf_has(O, rdf:type, Rng).
1904
1905
1906triple_header(Count, Pred, Dom, Range, Graph) -->
1907	html([ 'Table for the ~D triples'-[Count],
1908	       \for_predicate(Pred),
1909	       \with_domain(Dom),
1910	       \with_range(Range),
1911	       \in_graph(Graph)
1912	     ]).
1913
1914with_domain(Dom) -->
1915	{ var(Dom) }, !.
1916with_domain(Dom) -->
1917	html([' with domain ', \rdf_link(Dom, [])]).
1918
1919with_range(Range) -->
1920	{ var(Range) }, !.
1921with_range(Range) -->
1922	html([' with range ', \rdf_link(Range, [])]).
1923
1924%%	triple_table(+Triples, +Predicate, +Options)// is det.
1925%
1926%	Show a list  of  triples.  If   Predicate  is  given,  omit  the
1927%	predicate from the table.
1928
1929triple_table(Triples, Pred, Options) -->
1930	{ option(top_max(TopMax), Options, 500),
1931	  option(top_max(BottomMax), Options, 500)
1932	},
1933	html(table(class(block),
1934		   [ \spo_header(Pred)
1935		   | \table_rows_top_bottom(spo_row(Options, Pred), Triples,
1936					    TopMax, BottomMax)
1937		   ])).
1938
1939spo_header(P) -->
1940	{ nonvar(P) },
1941	html(tr([ th('Subject'),
1942		  th('Object')
1943		])).
1944spo_header(_) -->
1945	html(tr([ th('Subject'),
1946		  th('Predicate'),
1947		  th('Object')
1948		])).
1949
1950spo_row(Options, Pred, rdf(S,_,O)) -->
1951	{ nonvar(Pred) }, !,
1952	html([ td(class(subject), \rdf_link(S, Options)),
1953	       td(class(object),  \rdf_link(O, Options))
1954	     ]).
1955spo_row(Options, _, rdf(S,P,O)) -->
1956	html([ td(class(subject),   \rdf_link(S, Options)),
1957	       td(class(predicate), \rdf_link(P, Options)),
1958	       td(class(object),    \rdf_link(O, Options))
1959	     ]).
1960
1961
1962%%	list_triples_with_object(+Request)
1963%
1964%	HTTP handler that creates a  subject/predicate table for triples
1965%	that have the gived _object_. Object   is specified using either
1966%	the =r= or =l= parameter. Optionally,  results can be limited to
1967%	a predicate and/or graph.
1968
1969list_triples_with_object(Request) :-
1970	http_parameters(Request,
1971			[ r(RObject,   [optional(true),
1972					description('Object as resource (URI)')
1973				       ]),
1974			  l(LObject,   [optional(true),
1975					description('Object as literal (Prolog notation)')
1976				       ]),
1977			  p(P,         [optional(true),
1978					description('Limit to a given predicate (URI)')
1979				       ]),
1980			  graph(Graph, [optional(true),
1981					description('Limit to a given graph (URI)')
1982				       ]),
1983			  sortBy(Sort,
1984				 [ oneof([label, subject, predicate]),
1985				   default(label),
1986				   description('How to sort the result')
1987				 ])
1988			]),
1989	target_object(RObject, LObject, Object),
1990	list_triples_with_object(Object, P, Graph, [sortBy(Sort)]).
1991
1992target_object(RObject, _LObject, RObject) :-
1993	atom(RObject), !.
1994target_object(_, LObject, Object) :-
1995	atom(LObject), !,
1996	term_to_atom(Object, LObject).
1997target_object(_, _, _) :-
1998	throw(existence_error(http_parameter, r)).
1999
2000%%	list_triples_with_literal(+Request)
2001%
2002%	List triples that have a literal   that matches the q-parameter.
2003%	This is used for  finding   objects  through  the autocompletion
2004%	interface.
2005
2006list_triples_with_literal(Request) :-
2007	http_parameters(Request,
2008			[ q(Text,
2009			    [optional(true),
2010			     description('Object as resource (URI)')
2011			    ])
2012			]),
2013	list_triples_with_object(literal(Text), _, _, [sortBy(subject)]).
2014
2015
2016list_triples_with_object(Object, P, Graph, Options) :-
2017	findall(S-P, rdf(S,P,Object,Graph), Pairs),
2018	(   option(sortBy(label), Options)
2019	->  sort_pairs_by_label(Pairs, Sorted)
2020	;   option(sortBy(predicate), Options)
2021	->  transpose_pairs(Pairs, Transposed), % flip pairs and sort on new key
2022	    flip_pairs(Transposed, Sorted)      % flip back without sort
2023	;   sort(Pairs, Sorted)
2024	),
2025	length(Pairs, Count),
2026	label_of(Object, OLabel),
2027	reply_html_page(cliopatria(default),
2028			title('Triples with object ~w'-[OLabel]),
2029			[ h1(\otriple_header(Count, Object, P, Graph, Options)),
2030			  \otriple_table(Sorted, Object, [resource_format(nslabel)])
2031			]).
2032
2033otriple_header(Count, Object, Pred, Graph, Options) -->
2034	{ option(sortBy(SortBy), Options) },
2035	html([ 'Table for the ~D triples'-[Count],
2036	       \with_object(Object),
2037	       \on_predicate(Pred),
2038	       \in_graph(Graph),
2039	       \sorted_by(SortBy)
2040	     ]).
2041
2042with_object(Obj) -->
2043	{ var(Obj)}, !.
2044with_object(Obj) -->
2045	html([' with object ', \rdf_link(Obj)]).
2046
2047on_predicate(P) -->
2048	{ var(P) }, !.
2049on_predicate(P) -->
2050	html([' on predicate ', \rdf_link(P, [])]).
2051
2052
2053otriple_table(SPList, Object, Options) -->
2054	{ option(top_max(TopMax), Options, 500),
2055	  option(top_max(BottomMax), Options, 500)
2056	},
2057	html(table(class(block),
2058		   [ \sp_header(Object)
2059		   | \table_rows_top_bottom(sp_row(Options,Object), SPList,
2060					    TopMax, BottomMax)
2061		   ])).
2062
2063sp_header(_) -->
2064	html(tr([ th('Subject'),
2065		  th('Predicate')
2066		])).
2067
2068sp_row(Options, _O, S-P) -->
2069	html([ td(class(subject),   \rdf_link(S, Options)),
2070	       td(class(predicate), \rdf_link(P, Options))
2071	     ]).
2072
2073
2074
2075
2076
2077		 /*******************************
2078		 *	      RDF UTIL		*
2079		 *******************************/
2080
2081%%	sort_by_label(+URIs, -Sorted) is det.
2082%
2083%	Sort a list of URIs by their label using locale-based ordering.
2084
2085sort_by_label(URIs, Sorted) :-
2086	map_list_to_pairs(label_sort_key, URIs, LabelPairs),
2087	keysort(LabelPairs, SortedPairs),
2088	pairs_values(SortedPairs, Sorted).
2089
2090label_sort_key(URI, Key) :-
2091	label_of(URI, Label),
2092	(   atom(Label)
2093	->  collation_key(Label, Key)
2094	;   Key = Label
2095	).
2096
2097label_of(URI, Label) :-
2098	rdf_is_resource(URI), !,
2099	rdf_display_label(URI, Label).
2100label_of(Literal, Label) :-
2101	literal_text(Literal, Label).
2102
2103
2104%%	sort_triples_by_label(+Triples, -Sorted)
2105%
2106%	Sort a list of rdf(S,P,O) by the labels.
2107
2108sort_triples_by_label(Pairs, Sorted) :-
2109	map_list_to_pairs(key_triple_by_label, Pairs, LabelPairs),
2110	keysort(LabelPairs, SortedPairs),
2111	pairs_values(SortedPairs, Sorted).
2112
2113key_triple_by_label(rdf(S,P,O), rdf(SK,PK,OK)) :-
2114	label_sort_key(S, SK),
2115	label_sort_key(P, PK),
2116	label_sort_key(O, OK).
2117
2118%%	sort_pairs_by_label(+Pairs, -Sorted)
2119%
2120%	Sort a pair-list where the keys are resources by their label.
2121
2122sort_pairs_by_label(Pairs, Sorted) :-
2123	map_list_to_pairs(key_label_sort_key, Pairs, LabelPairs),
2124	keysort(LabelPairs, SortedPairs),
2125	pairs_values(SortedPairs, Sorted).
2126
2127key_label_sort_key(R-_, Key) :-
2128	label_sort_key(R, Key).
2129
2130
2131		 /*******************************
2132		 *	  CUSTOMIZATION		*
2133		 *******************************/
2134
2135%%	p_label(+Id, -Label)
2136%
2137%	Defines the visible label for a property.
2138%
2139%	@see	html_property_table//2.
2140
2141p_label(source(_), 'Source URL').
2142p_label(triples(G),
2143	['# ', a(href(Link), triples)]) :-
2144	http_link_to_id(list_triples, [graph=G], Link).
2145p_label(subject_count(G),
2146	['# ', a(href(Link), subjects)]) :-
2147	http_link_to_id(list_instances, [graph=G], Link).
2148p_label(bnode_count(G),
2149	['# ', a(href(Link), 'bnode subjects')]) :-
2150	http_link_to_id(list_instances, [graph=G, type=bnode], Link).
2151p_label(predicate_count(G),
2152	['# ', a(href(Link), predicates)]) :-
2153	http_link_to_id(list_predicates, [graph=G], Link).
2154p_label(type_count(G),
2155	['# Referenced ', a(href(Link), classes)]) :-
2156	http_link_to_id(list_classes, [graph=G], Link).
2157
2158
2159		 /*******************************
2160		 *	      SEARCH		*
2161		 *******************************/
2162
2163%%	search(+Request)
2164%
2165%	HTTP handler to search for triples   that contain a literal that
2166%	matches a query.
2167%
2168%	@tbd	Produce a sensible search language.
2169
2170search(Request) :-
2171	http_parameters(Request,
2172			[ q(QueryText,
2173			    [ description('Query to search for')
2174			    ]),
2175			  filter(FilterAtom,
2176				 [ optional(true),
2177				   description('Filter on raw matches (a Prolog term)')
2178				 ])
2179			]),
2180	(   var(FilterAtom)
2181	->  Filter = true
2182	;   atom_to_term(FilterAtom, Filter0, []),
2183	    rdf_global_term(Filter0, Filter)
2184	),
2185
2186	find_literals(QueryText, Literals, Query),
2187	literal_triples(Literals, Filter, Triples),
2188	reply_html_page(cliopatria(default),
2189			title('Search results for ~q'-[Query]),
2190			[ h1('Search results for token "~q"'-[Query]),
2191			  \rdf_table(Triples, [])
2192			]).
2193
2194find_literals(QueryText, [Query], exact(Query)) :-
2195	% Check if Q starts and ends with double quotes:
2196	sub_atom(QueryText,0,1,Remainder,'"'),
2197	sub_atom(QueryText,Remainder,1,0,'"'),!,
2198	sub_atom(QueryText,1,_,1,Query).
2199find_literals(QueryText, Literals, Query) :-
2200	% if not quoted, perform search on tokenized query
2201	tokenize_atom(QueryText, Tokens),
2202	once(phrase(query(Query), Tokens)),
2203	rdf_find_literals(Query, Literals).
2204
2205query(Query) -->
2206	simple_query(Q1),
2207	(   eos
2208	->  {Query = Q1}
2209	;   query(Q2),
2210	    {Query = and(Q1,Q2)}
2211	).
2212
2213eos([],[]).
2214
2215simple_query(Token) -->
2216	['"',Token,'"'], !.
2217simple_query(not(Token)) -->
2218	[-, Token].
2219simple_query(case(Token)) -->
2220	[Token].
2221
2222%%	literal_triples(+ListOfLiterals, +Filter, -Triples) is det.
2223%
2224%	Find the list of triples with   a  literal in ListOfLiterals and
2225%	whose subject satisfies Filter.
2226
2227literal_triples(Literals, Filter, Triples) :-
2228	sub_term(graph(Graph), Filter), !,
2229	phrase(ltriples(Literals, Graph, Filter), Triples).
2230literal_triples(Literals, Filter, Triples) :-
2231	phrase(ltriples(Literals, Filter), Triples).
2232
2233
2234ltriples([], _, _) --> [].
2235ltriples([H|T], G, F) -->
2236	findall(rdf(S,P,literal(L)),
2237		(   rdf(S,P,literal(exact(H), L),G),
2238		    search_filter(F, S)
2239		)),
2240	ltriples(T, G, F).
2241
2242ltriples([], _) --> [].
2243ltriples([H|T], F) -->
2244	findall(rdf(S,P,literal(L)),
2245		(   rdf(S,P,literal(exact(H), L)),
2246		    search_filter(F, S)
2247		)),
2248	ltriples(T, F).
2249
2250%%	rdf_table(+Triples, +Options)// is det.
2251%
2252%	Emit a table of triples.
2253%
2254%	@param Triples is a list of rdf(S,P,O).
2255
2256rdf_table(Triples, Options) -->
2257	{ option(top_max(TopMax), Options, 500),
2258	  option(top_max(BottomMax), Options, 500)
2259	},
2260	html(table(class(block),
2261		   [ tr([ th('Subject'), th('Predicate'), th('Object') ])
2262		   | \table_rows_top_bottom(triple, Triples,
2263					    TopMax, BottomMax)
2264		   ])).
2265
2266triple(rdf(S,P,O)) -->
2267	html([ td(class(subject),   \rdf_link(S)),
2268	       td(class(predicate), \rdf_link(P)),
2269	       td(class(object),    \rdf_link(O))
2270	     ]).
2271
2272
2273		 /*******************************
2274		 *     HTML INFRASTRUCTURE	*
2275		 *******************************/
2276
2277%%	html_property_table(+Template, :Goal)// is det.
2278%
2279%	Create a table for all instantiations of Template for which Goal
2280%	is true. Template is a term row(C1,   C2, ...). The first column
2281%	(C1) is considered the property-name and   emitted  as a cell of
2282%	class =p_name=. The label for  the   property  is  derived using
2283%	p_label/2. The remainder is emited as normal td value-cells.
2284
2285html_property_table(Template, Goal) -->
2286	{ findall(Template, Goal, Rows) },
2287	html(table(class(block),
2288		   \table_rows(prow, Rows))).
2289
2290prow(Row) -->
2291	{ Row =.. [_,H|Cells],
2292	  (   p_label(H, Label0)
2293	  ->  true
2294	  ;   functor(H, Label0, _)
2295	  ),
2296	  (   is_list(Label0)
2297	  ->  append(Label0, [:], Label)
2298	  ;   Label = [Label0, :]
2299	  )
2300	},
2301	html([ th(class(p_name), Label)
2302	     | \pcells(Cells)
2303	     ]).
2304
2305pcells([]) --> [].
2306pcells([H|T]) -->
2307	pcell(H),
2308	pcells(T).
2309
2310pcell(int(Value)) -->
2311	{ integer(Value) }, !,
2312	nc('~D', Value).
2313pcell(H) -->
2314	{ compound(H),
2315	  H =.. [Class,Value], !
2316	},
2317	html(td(class(Class), Value)).
2318pcell(H) -->
2319	html(td(H)).
2320
2321
2322%%	table_rows(:Goal, +DataList)// is det.
2323%%	table_rows(:Goal, +DataList, +MaxTop, +MaxBottom)// is det.
2324%
2325%	Emit a number of table rows (=tr=).   The content of each row is
2326%	created by calling call(Goal, Data)  as   a  DCG.  The rows have
2327%	alternating classes =even= and =odd=.  The first row is =odd=.
2328%
2329%	The variation table_rows//4  limits  the   size  of  the  table,
2330%	placing a cell with  class  =skip=,   indicating  the  number of
2331%	skipped rows.
2332%
2333%	Note that we can also achieve  alternate colouring using the CSS
2334%	pseudo classes =|tr:nth-child(odd)|= and =|tr:nth-child(even)|=.
2335
2336table_rows(Goal, Rows) -->
2337	table_rows(Rows, Goal, 1, -1).
2338
2339table_rows_top_bottom(Goal, Rows, inf, inf) --> !,
2340	table_rows(Rows, Goal, 1, -1).
2341table_rows_top_bottom(Goal, Rows, MaxTop, MaxBottom) -->
2342	{ length(Rows, Count) },
2343	(   { MaxTop+MaxBottom >= Count }
2344	->  table_rows(Rows, Goal, 1, -1)
2345	;   { Skip is Count-MaxBottom,
2346	      delete_list_prefix(Skip, Rows, BottomRows),
2347	      Skipped is Count-(MaxTop+MaxBottom)
2348	    },
2349	    table_rows(Rows, Goal, 1, MaxTop),
2350	    html(tr(class(skip),
2351		    [ th(colspan(10), 'Skipped ~D rows'-[Skipped])
2352		    ])),
2353	    table_rows(BottomRows, Goal, 1, -1)
2354	).
2355
2356table_rows(_, _, _, 0) --> !, [].
2357table_rows([], _, _, _) --> [].
2358table_rows([H|T], Goal, N, Left) -->
2359	{ N2 is N + 1,
2360	  (   N mod 2 =:= 0
2361	  ->  Class = even
2362	  ;   Class = odd
2363	  ),
2364	  Left2 is Left - 1
2365	},
2366	html(tr(class(Class), \call(Goal, H))),
2367	table_rows(T, Goal, N2, Left2).
2368
2369delete_list_prefix(0, List, List) :- !.
2370delete_list_prefix(_, [], []) :- !.
2371delete_list_prefix(N, [_|T], List) :-
2372	N2 is N - 1,
2373	delete_list_prefix(N2, T, List).
2374
2375%%	list_prefixes(+Request)
2376%
2377%	List known RDF prefixes in various formats
2378
2379list_prefixes(Request) :-
2380	Formats = [html,turtle],
2381	http_parameters(Request,
2382			[ format(Format,
2383				 [ oneof(Formats),
2384				   description('Output format'),
2385				   default(html)
2386				 ])
2387			]),
2388	findall(Prefix-URI,
2389		rdf_current_ns(Prefix, URI),
2390		Pairs),
2391	keysort(Pairs, Sorted),
2392	reply_html_page(cliopatria(default),
2393			title('RDF prefixes (namespaces)'),
2394			[ h1('Known RDF prefixes (namespaces)'),
2395			  \explain_prefixes,
2396			  \ns_table(Format, Sorted),
2397			  \prefix_formats(Formats, Format, Request)
2398			]).
2399
2400explain_prefixes -->
2401	html(p([ 'The following prefixes are known and may be used \c
2402	          without declaration in SPARQL queries to this server.'
2403	       ])).
2404
2405prefix_formats(Formats, Format, Request) -->
2406	{ select(Format, Formats, Alt)
2407	},
2408	html(p(class('prefix-format'),
2409	       [ 'Also available in ',
2410		 \alt_formats(Alt, Request)
2411	       ])).
2412
2413alt_formats([], _) --> [].
2414alt_formats([H|T], Request) -->
2415	{ http_reload_with_parameters(Request, [format(H)], HREF)
2416	},
2417	html(a(href(HREF), H)),
2418	(   {T==[]}
2419	->  []
2420	;   html(' and '),
2421	    alt_formats(T, Request)
2422	).
2423
2424ns_table(html, Pairs) -->
2425	html(table(class(block),
2426		   [ \prefix_table_header,
2427		     \table_rows(prefix_row, Pairs)
2428		   ])).
2429ns_table(turtle, Pairs) -->
2430	html(pre(class(code),
2431		 \turtle_prefixes(Pairs))).
2432
2433prefix_table_header -->
2434	html(tr([ th('Prefix'),
2435		  th('URI')
2436		])).
2437
2438prefix_row(Prefix-URI) -->
2439	html([ td(Prefix),
2440	       td(URI)
2441	     ]).
2442
2443turtle_prefixes(Pairs) -->
2444	{ longest_prefix(Pairs, 0, Length),
2445	  PrefixCol is Length+10
2446	},
2447	turtle_prefixes(Pairs, PrefixCol).
2448
2449longest_prefix([], L, L).
2450longest_prefix([Prefix-_|T], L0, L) :-
2451	atom_length(Prefix, L1),
2452	L2 is max(L0, L1),
2453	longest_prefix(T, L2, L).
2454
2455turtle_prefixes([], _) --> [].
2456turtle_prefixes([Prefix-URI|T], Col) -->
2457	html('@prefix ~t~w: ~*|<~w> .~n'-[Prefix, Col, URI]),
2458	turtle_prefixes(T, Col).