annotate jamendo/sparql-archived/SeRQL/lib/semweb/rdf_library.pl @ 27:d95e683fbd35 tip

Enable CORS on urispace redirects as well
author Chris Cannam
date Tue, 20 Feb 2018 14:52:02 +0000
parents df9685986338
children
rev   line source
Chris@0 1 /* $Id$
Chris@0 2
Chris@0 3 Part of SWI-Prolog
Chris@0 4
Chris@0 5 Author: Jan Wielemaker
Chris@0 6 E-mail: wielemak@science.uva.nl
Chris@0 7 WWW: http://www.swi-prolog.org
Chris@0 8 Copyright (C): 2007, University of Amsterdam
Chris@0 9
Chris@0 10 This program is free software; you can redistribute it and/or
Chris@0 11 modify it under the terms of the GNU General Public License
Chris@0 12 as published by the Free Software Foundation; either version 2
Chris@0 13 of the License, or (at your option) any later version.
Chris@0 14
Chris@0 15 This program is distributed in the hope that it will be useful,
Chris@0 16 but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 18 GNU General Public License for more details.
Chris@0 19
Chris@0 20 You should have received a copy of the GNU Lesser General Public
Chris@0 21 License along with this library; if not, write to the Free Software
Chris@0 22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Chris@0 23
Chris@0 24 As a special exception, if you link this library with other files,
Chris@0 25 compiled with a Free Software compiler, to produce an executable, this
Chris@0 26 library does not by itself cause the resulting executable to be covered
Chris@0 27 by the GNU General Public License. This exception does not however
Chris@0 28 invalidate any other reasons why the executable file might be covered by
Chris@0 29 the GNU General Public License.
Chris@0 30 */
Chris@0 31
Chris@0 32 :- module(rdf_library,
Chris@0 33 [ rdf_attach_library/1, % +Dir
Chris@0 34 rdf_load_library/1, % +Ontology
Chris@0 35 rdf_load_library/2, % +Ontology, +Options
Chris@0 36 rdf_list_library/0,
Chris@0 37 rdf_list_library/1, % +Ontology
Chris@0 38 rdf_list_library/2, % +Ontology, +Options
Chris@0 39 rdf_library_index/2 % ?Id, ?Facet
Chris@0 40 ]).
Chris@0 41 :- use_module(library('semweb/rdf_db')).
Chris@0 42 :- use_module(library('semweb/rdf_turtle')).
Chris@0 43 :- use_module(library(rdf)).
Chris@0 44 :- use_module(library(lists)).
Chris@0 45 :- use_module(library(option)).
Chris@0 46 :- use_module(library(debug)).
Chris@0 47 :- use_module(library(error)).
Chris@0 48 :- use_module(library(pairs)).
Chris@0 49 :- use_module(library(date)).
Chris@0 50 :- use_module(library(url)).
Chris@0 51 :- use_module(library(http/http_open)).
Chris@0 52 :- use_module(library(thread)).
Chris@0 53
Chris@0 54 /** <module> RDF Library Manager
Chris@0 55
Chris@0 56 This module manages an ontology library. Such a library consists of a
Chris@0 57 directory with manifest files named =manifest,rdf= or =manifest.ttl=
Chris@0 58 (Turtle). The manifest files define ontologies appearing in the library
Chris@0 59 as well as namespace mnemonics and dependencies.
Chris@0 60
Chris@0 61 The typical usage scenario is
Chris@0 62
Chris@0 63 ==
Chris@0 64 ?- rdf_attach_library('/some/directory').
Chris@0 65 ?- rdf_load_library(my_ontology).
Chris@0 66 ==
Chris@0 67
Chris@0 68
Chris@0 69 @tbd Add caching info
Chris@0 70 @tbd Allow HTTP-hosted repositories
Chris@0 71
Chris@0 72 @author Jan Wielemaker
Chris@0 73 */
Chris@0 74
Chris@0 75 :- rdf_register_ns(lib, 'http://www.swi-prolog.org/rdf/library/').
Chris@0 76
Chris@0 77 :- dynamic
Chris@0 78 manifest/2, % Path, Time
Chris@0 79 library_db/3. % Name, URL, Facets
Chris@0 80
Chris@0 81 % Force compile-time namespace expansion
Chris@0 82
Chris@0 83 :- rdf_meta
Chris@0 84 edge(+, r,r,o).
Chris@0 85
Chris@0 86 /*******************************
Chris@0 87 * LOADING *
Chris@0 88 *******************************/
Chris@0 89
Chris@0 90 %% rdf_load_library(+Id) is det.
Chris@0 91 %% rdf_load_library(+Id, +Options) is det.
Chris@0 92 %
Chris@0 93 % Load ontologies from the library. A library must first be
Chris@0 94 % attached using rdf_attach_library/1. Defined Options are:
Chris@0 95 %
Chris@0 96 % * import(Bool)
Chris@0 97 % If =true= (default), also load ontologies that are
Chris@0 98 % explicitely imported.
Chris@0 99 %
Chris@0 100 % * base_uri(URI)
Chris@0 101 % BaseURI used for loading RDF. Local definitions in
Chris@0 102 % ontologies overrule this option.
Chris@0 103 %
Chris@0 104 % * claimed_source(URL)
Chris@0 105 % URL from which we claim to have loaded the data.
Chris@0 106 %
Chris@0 107 % * not_found(+Level)
Chris@0 108 % The system does a pre-check for the existence of
Chris@0 109 % all references RDF databases. If Level is =error=
Chris@0 110 % it reports missing databases as an error and fails.
Chris@0 111 % If =warning= it prints them, but continues. If
Chris@0 112 % =silent=, no checks are preformed. Default is =error=.
Chris@0 113 %
Chris@0 114 % * concurrent(Threads)
Chris@0 115 % Perform the load concurrently using N threads. If not
Chris@0 116 % specified, the number is determined by
Chris@0 117 % guess_concurrency/2.
Chris@0 118 %
Chris@0 119 % * load(+Bool)
Chris@0 120 % If =false=, to all the preparation, but do not execute
Chris@0 121 % the actual loading. See also rdf_list_library/2.
Chris@0 122
Chris@0 123 rdf_load_library(Id) :-
Chris@0 124 rdf_load_library(Id, []).
Chris@0 125
Chris@0 126 rdf_load_library(Id, Options) :-
Chris@0 127 load_commands(Id, Options, Pairs),
Chris@0 128 pairs_values(Pairs, Commands),
Chris@0 129 list_to_set(Commands, Cmds2),
Chris@0 130 delete_virtual(Cmds2, Cmds3),
Chris@0 131 find_conflicts(Cmds3),
Chris@0 132 check_existence(Cmds3, Cmds, Options),
Chris@0 133 ( option(concurrent(Threads), Options)
Chris@0 134 -> true
Chris@0 135 ; guess_concurrency(Cmds, Threads)
Chris@0 136 ),
Chris@0 137 length(Cmds, NSources),
Chris@0 138 print_message(informational, rdf(loading(NSources, Threads))),
Chris@0 139 ( option(load(true), Options, true)
Chris@0 140 -> concurrent(Threads, Cmds,
Chris@0 141 [ local(2000), % we only need small stacks
Chris@0 142 global(4000),
Chris@0 143 trail(4000)
Chris@0 144 ])
Chris@0 145 ; true
Chris@0 146 ).
Chris@0 147
Chris@0 148 delete_virtual([], []).
Chris@0 149 delete_virtual([virtual(_)|T0], T) :- !,
Chris@0 150 delete_virtual(T0, T).
Chris@0 151 delete_virtual([H|T0], [H|T]) :-
Chris@0 152 delete_virtual(T0, T).
Chris@0 153
Chris@0 154
Chris@0 155 %% find_conflicts(+LoadCommands) is semidet.
Chris@0 156 %
Chris@0 157 % Find possibly conflicting options for loading the same source
Chris@0 158
Chris@0 159 find_conflicts(Commands) :-
Chris@0 160 sort(Commands, Cmds),
Chris@0 161 conflicts(Cmds, Conflicts),
Chris@0 162 report_conflics(Conflicts),
Chris@0 163 Conflicts == [].
Chris@0 164
Chris@0 165 conflicts([], []).
Chris@0 166 conflicts([C1, C2|T0], [C1-C2|T]) :-
Chris@0 167 conflict(C1, C2), !,
Chris@0 168 conflicts([C2|T0], T).
Chris@0 169 conflicts([_|T0], T) :-
Chris@0 170 conflicts(T0, T).
Chris@0 171
Chris@0 172 conflict(rdf_load(Src, Options1), rdf_load(Src, Options2)) :-
Chris@0 173 sort(Options1, S1),
Chris@0 174 sort(Options2, S2),
Chris@0 175 S1 \== S2.
Chris@0 176
Chris@0 177 report_conflics([]).
Chris@0 178 report_conflics([C1-C2|T]) :-
Chris@0 179 print_message(warning, rdf(load_conflict(C1,C2))),
Chris@0 180 report_conflics(T).
Chris@0 181
Chris@0 182
Chris@0 183 %% check_existence(+CommandsIn, -Commands, +Options) is det.
Chris@0 184 %
Chris@0 185 % Report existence errors. Fail if at least one source does not
Chris@0 186 % exist. and the not_found level is not =silent=.
Chris@0 187 %
Chris@0 188 % @error existence_error(urls, ListOfUrls)
Chris@0 189
Chris@0 190 check_existence(CommandsIn, Commands, Options) :-
Chris@0 191 option(not_found(Level), Options, error),
Chris@0 192 must_be(oneof([error,warning,silent]), Level),
Chris@0 193 ( Level == silent
Chris@0 194 -> true
Chris@0 195 ; missing_urls(CommandsIn, Commands, Missing),
Chris@0 196 ( Missing == []
Chris@0 197 -> true
Chris@0 198 ; Level == warning
Chris@0 199 -> report_missing(Missing, Level)
Chris@0 200 ; existence_error(urls, Missing)
Chris@0 201 )
Chris@0 202 ).
Chris@0 203
Chris@0 204
Chris@0 205 missing_urls([], [], []).
Chris@0 206 missing_urls([H|T0], Cmds, Missing) :-
Chris@0 207 H = rdf_load(URL, _),
Chris@0 208 ( exists_url(URL)
Chris@0 209 -> Cmds = [H|T],
Chris@0 210 missing_urls(T0, T, Missing)
Chris@0 211 ; Missing = [URL|T],
Chris@0 212 missing_urls(T0, Cmds, T)
Chris@0 213 ).
Chris@0 214
Chris@0 215 report_missing([], _).
Chris@0 216 report_missing([H|T], Level) :-
Chris@0 217 print_message(Level, error(existence_error(url, H), _)),
Chris@0 218 report_missing(T, Level).
Chris@0 219
Chris@0 220 %% guess_concurrency(+Commands, -Threads) is det.
Chris@0 221 %
Chris@0 222 % How much concurrency to use? Set to the number of CPUs if all
Chris@0 223 % input comes from files or 5 if network based loading is
Chris@0 224 % demanded.
Chris@0 225
Chris@0 226 guess_concurrency(Commands, Threads) :-
Chris@0 227 count_non_file_url(Commands, Count),
Chris@0 228 ( current_prolog_flag(cpu_count, CPUs)
Chris@0 229 -> true
Chris@0 230 ; CPUs = 1
Chris@0 231 ),
Chris@0 232 Threads is max(CPUs, min(5, Count)).
Chris@0 233
Chris@0 234 count_non_file_url([], 0).
Chris@0 235 count_non_file_url([rdf_load(URL, _)|T], Count) :-
Chris@0 236 sub_atom(URL, 0, _, _, 'file://'), !,
Chris@0 237 count_non_file_url(T, Count).
Chris@0 238 count_non_file_url([_|T], Count) :-
Chris@0 239 count_non_file_url(T, C0),
Chris@0 240 Count is C0 + 1.
Chris@0 241
Chris@0 242
Chris@0 243 %% load_commands(+Id, +Options, -Pairs:list(Level-Command)) is det.
Chris@0 244 %
Chris@0 245 % Commands are the RDF commands to execute for rdf_load_library/2.
Chris@0 246 % Splitting in command collection and execution allows for
Chris@0 247 % concurrent execution as well as forward checking of possible
Chris@0 248 % problems.
Chris@0 249 %
Chris@0 250 % @tbd Fix poor style; avoid assert/retract.
Chris@0 251
Chris@0 252 :- thread_local
Chris@0 253 command/2.
Chris@0 254
Chris@0 255 load_commands(Id, Options, Commands) :-
Chris@0 256 retractall(command(_,_)),
Chris@0 257 rdf_update_library_index,
Chris@0 258 dry_load(Id, 1, Options),
Chris@0 259 findall(Level-Cmd, retract(command(Level, Cmd)), Commands).
Chris@0 260
Chris@0 261 dry_load(Id, Level, Options) :-
Chris@0 262 ( library(Id, File, Facets)
Chris@0 263 -> merge_base_uri(Facets, Options, Options1),
Chris@0 264 merge_source(Facets, Options1, Options2),
Chris@0 265 merge_blanks(Facets, Options2, Options3),
Chris@0 266 ( \+ memberchk(virtual, Facets)
Chris@0 267 -> load_options(Options3, File, RdfOptions),
Chris@0 268 assert(command(Level, rdf_load(File, RdfOptions)))
Chris@0 269 ; assert(command(Level, virtual(File)))
Chris@0 270 ),
Chris@0 271 ( option(import(true), Options, true)
Chris@0 272 -> Level1 is Level + 1,
Chris@0 273 forall(member(imports(_, Import), Facets),
Chris@0 274 import(Import, Level1, Options3))
Chris@0 275 ; true
Chris@0 276 )
Chris@0 277 ; existence_error(ontology, Id)
Chris@0 278 ).
Chris@0 279
Chris@0 280 merge_base_uri(Facets, Options0, Options) :-
Chris@0 281 ( option(base_uri(Base), Facets)
Chris@0 282 -> delete(Options0, base_uri(_), Options1),
Chris@0 283 Options = [base_uri(Base)|Options1]
Chris@0 284 ; Options = Options0
Chris@0 285 ).
Chris@0 286
Chris@0 287 merge_source(Facets, Options0, Options) :-
Chris@0 288 ( option(claimed_source(Base), Facets)
Chris@0 289 -> delete(Options0, claimed_source(_), Options1),
Chris@0 290 Options = [claimed_source(Base)|Options1]
Chris@0 291 ; Options = Options0
Chris@0 292 ).
Chris@0 293
Chris@0 294 merge_blanks(Facets, Options0, Options) :-
Chris@0 295 ( option(blank_nodes(Share), Facets)
Chris@0 296 -> delete(Options0, blank_nodes(_), Options1),
Chris@0 297 Options = [blank_nodes(Share)|Options1]
Chris@0 298 ; Options = Options0
Chris@0 299 ).
Chris@0 300
Chris@0 301 load_options(Options, File, RDFOptions) :-
Chris@0 302 findall(O, load_option(Options, File, O), RDFOptions).
Chris@0 303
Chris@0 304 load_option(Options, File, db(Source)) :-
Chris@0 305 option(claimed_source(Source0), Options),
Chris@0 306 ( sub_atom(Source0, _, _, 0, /)
Chris@0 307 -> file_base_name(File, Base),
Chris@0 308 atom_concat(Source0, Base, Source)
Chris@0 309 ; atom_concat(Source, #, Source0)
Chris@0 310 -> true
Chris@0 311 ).
Chris@0 312 load_option(Options, File, base_uri(BaseURI)) :-
Chris@0 313 option(base_uri(Base0), Options),
Chris@0 314 sub_atom(/, _, _, 0, Base0),
Chris@0 315 atom_concat(Base0, File, BaseURI).
Chris@0 316 load_option(Options, _File, blank_nodes(Share)) :-
Chris@0 317 option(blank_nodes(Share), Options).
Chris@0 318
Chris@0 319 %% import(+URL, +Level, +Options) is det.
Chris@0 320
Chris@0 321 import(Path, Level, Options) :-
Chris@0 322 ( ( library(Id, Path, _)
Chris@0 323 -> true
Chris@0 324 ; manifest_for_path(Path, Manifest),
Chris@0 325 catch(exists_url(Manifest), _, fail)
Chris@0 326 -> process_manifest(Manifest),
Chris@0 327 library(Id, Path, _)
Chris@0 328 )
Chris@0 329 -> dry_load(Id, Level, Options)
Chris@0 330 ; load_options(Options, Path, RdfOptions),
Chris@0 331 assert(command(Level, rdf_load(Path, RdfOptions)))
Chris@0 332 ).
Chris@0 333
Chris@0 334 manifest_for_path(URL, Manifest) :-
Chris@0 335 file_directory_name(URL, Parent),
Chris@0 336 manifest_file(Base),
Chris@0 337 rdf_extension(Ext),
Chris@0 338 concat_atom([Parent, /, Base, '.', Ext], Manifest).
Chris@0 339
Chris@0 340 %% rdf_list_library(+Id) is det.
Chris@0 341 %% rdf_list_library(+Id, +Options) is det.
Chris@0 342 %
Chris@0 343 % Print library dependency tree to the terminal. Options include
Chris@0 344 % options for rdf_load_library/2 and
Chris@0 345 %
Chris@0 346 % * show_source(+Boolean)
Chris@0 347 % If =true= (default), show location we are loading
Chris@0 348 %
Chris@0 349 % * show_graph(+Boolean)
Chris@0 350 % If =true= (default =false=), show name of graph
Chris@0 351 %
Chris@0 352 % * show_virtual(+Boolean)
Chris@0 353 % If =false= (default =true=), do not show virtual
Chris@0 354 % repositories.
Chris@0 355 %
Chris@0 356 % * indent(Atom)
Chris@0 357 % Atom repeated for indentation levels
Chris@0 358
Chris@0 359 rdf_list_library(Id) :-
Chris@0 360 rdf_list_library(Id, []).
Chris@0 361 rdf_list_library(Id, Options) :-
Chris@0 362 load_commands(Id, Options, Commands),
Chris@0 363 maplist(print_load(Options), Commands).
Chris@0 364
Chris@0 365 print_load(Options, _Level-virtual(_)) :-
Chris@0 366 option(show_virtual(false), Options), !.
Chris@0 367 print_load(Options, Level-Command) :-
Chris@0 368 option(indent(Indent), Options, '. '),
Chris@0 369 forall(between(2, Level, _), format(Indent)),
Chris@0 370 print_command(Command, Options),
Chris@0 371 format('~N').
Chris@0 372
Chris@0 373 print_command(virtual(URL), _Options) :-
Chris@0 374 format('<~w>', [URL]).
Chris@0 375 print_command(rdf_load(URL), Options) :-
Chris@0 376 print_command(rdf_load(URL, []), Options).
Chris@0 377 print_command(rdf_load(URL, RDFOptions), Options) :-
Chris@0 378 ( option(show_source(true), Options, true)
Chris@0 379 -> format('~w', [URL]),
Chris@0 380 ( option(blank_nodes(noshare), RDFOptions)
Chris@0 381 -> format(' <not shared>')
Chris@0 382 ; true
Chris@0 383 ),
Chris@0 384 ( exists_url(URL)
Chris@0 385 -> true
Chris@0 386 ; format(' [NOT FOUND]')
Chris@0 387 )
Chris@0 388 ; true
Chris@0 389 ),
Chris@0 390 ( option(show_graph(true), Options, false),
Chris@0 391 option(db(Base), RDFOptions)
Chris@0 392 -> format('~N\tSource: ~w', [Base])
Chris@0 393 ; true
Chris@0 394 ).
Chris@0 395
Chris@0 396 exists_url(URL) :-
Chris@0 397 rdf_db:rdf_input(URL, Source, _BaseURI),
Chris@0 398 exists_source(Source).
Chris@0 399
Chris@0 400 exists_source(file(Path)) :- !,
Chris@0 401 access_file(Path, read).
Chris@0 402 exists_source(url(http, URL)) :- !,
Chris@0 403 catch(http_open(URL, Stream, [ method(head) ]), _, fail),
Chris@0 404 close(Stream).
Chris@0 405
Chris@0 406
Chris@0 407 %% rdf_list_library
Chris@0 408 %
Chris@0 409 % Prints known RDF library identifiers to current output.
Chris@0 410
Chris@0 411 rdf_list_library :-
Chris@0 412 rdf_update_library_index,
Chris@0 413 ( rdf_library_index(Id, title(Title)),
Chris@0 414 format('~w ~t~20|~w', [Id, Title]),
Chris@0 415 ( rdf_library_index(Id, version(Version))
Chris@0 416 -> format(' (version ~w)', [Version])
Chris@0 417 ; true
Chris@0 418 ),
Chris@0 419 nl,
Chris@0 420 fail
Chris@0 421 ; true
Chris@0 422 ).
Chris@0 423
Chris@0 424
Chris@0 425 %% rdf_library_index(?Id, ?Facet) is nondet.
Chris@0 426 %
Chris@0 427 % Query the content of the library. Defined facets are:
Chris@0 428 %
Chris@0 429 % * source(URL)
Chris@0 430 % Location from which to load the ontology
Chris@0 431 %
Chris@0 432 % * title(Atom)
Chris@0 433 % Title used for the ontology
Chris@0 434 %
Chris@0 435 % * comment(Atom)
Chris@0 436 % Additional comments for the ontology
Chris@0 437 %
Chris@0 438 % * version(Atom)
Chris@0 439 % Version information on the ontology
Chris@0 440 %
Chris@0 441 % * imports(Type, URL)
Chris@0 442 % URLs needed by this ontology. May succeed multiple
Chris@0 443 % times. Type is one of =ontology=, =schema= or =instances=.
Chris@0 444 %
Chris@0 445 % * base_uri(BaseURI)
Chris@0 446 % Base URI to use when loading documents. If BaseURI
Chris@0 447 % ends in =|/|=, the actual filename is attached.
Chris@0 448 %
Chris@0 449 % * claimed_source(Source)
Chris@0 450 % URL from which we claim to have loaded the RDF. If
Chris@0 451 % Source ends in =|/|=, the actual filename is
Chris@0 452 % attached.
Chris@0 453 %
Chris@0 454 % * blank_nodes(Share)
Chris@0 455 % Defines how equivalent blank nodes are handled, where
Chris@0 456 % Share is one of =share= or =noshare=. Default is to
Chris@0 457 % share.
Chris@0 458 %
Chris@0 459 % * provides_ns(URL)
Chris@0 460 % Ontology provides definitions in the namespace URL.
Chris@0 461 % The formal definition of this is troublesome, but in
Chris@0 462 % practice it means the ontology has triples whose
Chris@0 463 % subjects are in the given namespace.
Chris@0 464 %
Chris@0 465 % * uses_ns(URL)
Chris@0 466 % The ontology depends on the given namespace. Normally
Chris@0 467 % means it contains triples that have predicates or
Chris@0 468 % objects in the given namespace.
Chris@0 469 %
Chris@0 470 % * manifest(Path)
Chris@0 471 % Manifest file this ontology is defined in
Chris@0 472 %
Chris@0 473 % * virtual
Chris@0 474 % Entry is virtual (cannot be loaded)
Chris@0 475
Chris@0 476 rdf_library_index(Id, Facet) :-
Chris@0 477 library(Id, Path, Facets),
Chris@0 478 ( Facet = source(Path)
Chris@0 479 ; member(Facet, Facets)
Chris@0 480 ).
Chris@0 481
Chris@0 482
Chris@0 483 /*******************************
Chris@0 484 * MANIFEST PROCESSING *
Chris@0 485 *******************************/
Chris@0 486
Chris@0 487 %% rdf_attach_library(+Source)
Chris@0 488 %
Chris@0 489 % Attach manifest from Source. Source is one of
Chris@0 490 %
Chris@0 491 % * URL
Chris@0 492 % Load single manifest from this URL
Chris@0 493 % * File
Chris@0 494 % Load single manifest from this file
Chris@0 495 % * Directory
Chris@0 496 % Scan all subdirectories and load all =|Manifest.ttl|= or
Chris@0 497 % =|Manifest.rdf|= found.
Chris@0 498 %
Chris@0 499 % Encountered namespaces are registered using rdf_register_ns/2.
Chris@0 500 % Encountered ontologies are added to the index. If a manifest was
Chris@0 501 % already loaded it will be reloaded if the modification time has
Chris@0 502 % changed.
Chris@0 503
Chris@0 504 rdf_attach_library(URL) :-
Chris@0 505 atom(URL),
Chris@0 506 is_absolute_url(URL), !,
Chris@0 507 process_manifest(URL).
Chris@0 508 rdf_attach_library(File) :-
Chris@0 509 absolute_file_name(File, Path,
Chris@0 510 [ extensions([rdf,ttl]),
Chris@0 511 access(read),
Chris@0 512 file_errors(fail)
Chris@0 513 ]), !,
Chris@0 514 process_manifest(Path).
Chris@0 515 rdf_attach_library(Dir) :-
Chris@0 516 absolute_file_name(Dir, Path,
Chris@0 517 [ file_type(directory),
Chris@0 518 access(read)
Chris@0 519 ]),
Chris@0 520 attach_dir(Path, []).
Chris@0 521
Chris@0 522
Chris@0 523 %% rdf_update_library_index
Chris@0 524 %
Chris@0 525 % Reload all Manifest files.
Chris@0 526
Chris@0 527 rdf_update_library_index :-
Chris@0 528 forall(manifest(Location, _Time),
Chris@0 529 process_manifest(Location)).
Chris@0 530
Chris@0 531 attach_dir(Path, Visited) :-
Chris@0 532 memberchk(Path, Visited), !.
Chris@0 533 attach_dir(Path, Visited) :-
Chris@0 534 atom_concat(Path, '/*', Pattern),
Chris@0 535 expand_file_name(Pattern, Members),
Chris@0 536 ( member(Manifest, Members),
Chris@0 537 is_manifest_file(Manifest)
Chris@0 538 -> process_manifest(Manifest)
Chris@0 539 ; print_message(silent, rdf(no_manifest(Path)))
Chris@0 540 ),
Chris@0 541 ( member(Dir, Members),
Chris@0 542 exists_directory(Dir),
Chris@0 543 file_base_name(Dir, Base),
Chris@0 544 \+ hidden_base(Base),
Chris@0 545 attach_dir(Dir, [Path|Visited]),
Chris@0 546 fail ; true
Chris@0 547 ).
Chris@0 548
Chris@0 549 hidden_base('CVS').
Chris@0 550 hidden_base('cvs'). % Windows
Chris@0 551
Chris@0 552 %% process_manifest(+Location) is det.
Chris@0 553 %
Chris@0 554 % Process a manifest file, registering encountered namespaces and
Chris@0 555 % creating clauses for library/3. No op if manifest was loaded and
Chris@0 556 % not changed. Removes old data if the manifest was changed.
Chris@0 557 %
Chris@0 558 % @param Location is either a path name or a URL.
Chris@0 559
Chris@0 560 process_manifest(Source) :-
Chris@0 561 ( file_name_to_url(Manifest0, Source)
Chris@0 562 -> absolute_file_name(Manifest0, Manifest)
Chris@0 563 ; Manifest = Source
Chris@0 564 ),
Chris@0 565 source_time(Manifest, MT),
Chris@0 566 ( manifest(Manifest, Time),
Chris@0 567 ( MT =< Time
Chris@0 568 -> !
Chris@0 569 ; retractall(manifest(Manifest, Time)),
Chris@0 570 library_db(Id, URL, Facets),
Chris@0 571 memberchk(manifest(Manifest), Facets),
Chris@0 572 retractall(library_db(Id, URL, Facets)),
Chris@0 573 fail
Chris@0 574 )
Chris@0 575 ; read_triples(Manifest, Triples),
Chris@0 576 process_triples(Manifest, Triples),
Chris@0 577 print_message(informational, rdf(manifest(loaded, Manifest))),
Chris@0 578 assert(manifest(Manifest, MT))
Chris@0 579 ).
Chris@0 580
Chris@0 581 process_triples(Manifest, Triples) :-
Chris@0 582 findall(ns(Mnemonic, NameSpace),
Chris@0 583 extract_namespace(Triples, Mnemonic, NameSpace),
Chris@0 584 NameSpaces),
Chris@0 585 findall(Ontology,
Chris@0 586 extract_ontology(Triples, Ontology),
Chris@0 587 Ontologies),
Chris@0 588 maplist(define_namespace, NameSpaces),
Chris@0 589 maplist(assert_ontology(Manifest), Ontologies).
Chris@0 590
Chris@0 591 %% extract_namespace(+Triples, -Mnemonic, -NameSpace)
Chris@0 592 %
Chris@0 593 % True if Mnemonic is an abbreviation of NameSpace.
Chris@0 594
Chris@0 595 extract_namespace(Triples, Mnemonic, Namespace) :-
Chris@0 596 edge(Triples, Decl, lib:mnemonic, literal(Mnemonic)),
Chris@0 597 edge(Triples, Decl, lib:namespace, Namespace).
Chris@0 598
Chris@0 599 %% extract_ontology(+Triples, -Ontology) is nondet.
Chris@0 600 %
Chris@0 601 % Extract definition of an ontology
Chris@0 602
Chris@0 603 extract_ontology(Triples, library(Name, URL, Options)) :-
Chris@0 604 edge(Triples, URL, rdf:type, Type),
Chris@0 605 ( ontology_type(Type)
Chris@0 606 -> file_base_name(URL, BaseName),
Chris@0 607 file_name_extension(Name, _, BaseName),
Chris@0 608 findall(Facet, facet(Triples, URL, Facet), Options)
Chris@0 609 ).
Chris@0 610
Chris@0 611 ontology_type(X) :-
Chris@0 612 ( rdf_equal(X, lib:'Ontology')
Chris@0 613 ; rdf_equal(X, lib:'Schema')
Chris@0 614 ; rdf_equal(X, lib:'Instances')
Chris@0 615 ).
Chris@0 616
Chris@0 617 %% facet(+Triples, +File, -Facet) is nondet.
Chris@0 618 %
Chris@0 619 % Enumerate facets about File from Triples. Facets are described
Chris@0 620 % with rdf_library_index/2.
Chris@0 621
Chris@0 622 facet(Triples, File, title(Title)) :-
Chris@0 623 edge(Triples, File, dc:title, literal(Title)).
Chris@0 624 facet(Triples, File, version(Version)) :-
Chris@0 625 edge(Triples, File, owl:versionInfo, literal(Version)).
Chris@0 626 facet(Triples, File, comment(Comment)) :-
Chris@0 627 edge(Triples, File, rdfs:comment, literal(Comment)).
Chris@0 628 facet(Triples, File, base_uri(BaseURI)) :-
Chris@0 629 edge(Triples, File, lib:baseURI, BaseURI).
Chris@0 630 facet(Triples, File, claimed_source(Source)) :-
Chris@0 631 edge(Triples, File, lib:source, Source).
Chris@0 632 facet(Triples, File, blank_nodes(Mode)) :-
Chris@0 633 edge(Triples, File, lib:blankNodes, literal(Mode)),
Chris@0 634 must_be(oneof([share,noshare]), Mode).
Chris@0 635 facet(Triples, File, imports(ontology, Path)) :-
Chris@0 636 edge(Triples, File, owl:imports, Path).
Chris@0 637 facet(Triples, File, imports(schema, Path)) :-
Chris@0 638 edge(Triples, File, lib:schema, Path).
Chris@0 639 facet(Triples, File, imports(instances, Path)) :-
Chris@0 640 edge(Triples, File, lib:instances, Path).
Chris@0 641 facet(Triples, File, provides_ns(NS)) :-
Chris@0 642 edge(Triples, File, lib:providesNamespace, NSDecl),
Chris@0 643 edge(Triples, NSDecl, lib:namespace, NS).
Chris@0 644 facet(Triples, File, uses_ns(NS)) :-
Chris@0 645 edge(Triples, File, lib:usesNamespace, NSDecl),
Chris@0 646 edge(Triples, NSDecl, lib:namespace, NS).
Chris@0 647 facet(Triples, File, virtual) :-
Chris@0 648 edge(Triples, File, rdf:type, lib:'Virtual').
Chris@0 649
Chris@0 650 %% edge(+Triples, ?S, ?P, ?O) is nondet.
Chris@0 651 %
Chris@0 652 % Like rdf/3 over a list of Triples.
Chris@0 653
Chris@0 654 edge(Triples, S, P, O) :-
Chris@0 655 member(rdf(S,P,O), Triples).
Chris@0 656
Chris@0 657 %% source_time(+Source, -Modified) is semidet.
Chris@0 658 %
Chris@0 659 % Modified is the last modification time of Source.
Chris@0 660 %
Chris@0 661 % @error existence_error(Type, Source).
Chris@0 662
Chris@0 663 source_time(URL, Modified) :-
Chris@0 664 sub_atom(URL, 0, _, _, 'http://'), !,
Chris@0 665 http_open(URL, Stream,
Chris@0 666 [ header(last_modified, Date),
Chris@0 667 method(head)
Chris@0 668 ]),
Chris@0 669 close(Stream),
Chris@0 670 Date \== '',
Chris@0 671 parse_time(Date, Modified).
Chris@0 672 source_time(URL, Modified) :-
Chris@0 673 file_name_to_url(File, URL), !,
Chris@0 674 time_file(File, Modified).
Chris@0 675 source_time(File, Modified) :-
Chris@0 676 time_file(File, Modified).
Chris@0 677
Chris@0 678
Chris@0 679 %% read_triples(+File, -Triples) is det.
Chris@0 680 %
Chris@0 681 % Read RDF/XML or Turtle file into a list of triples.
Chris@0 682
Chris@0 683 read_triples(File, Triples) :-
Chris@0 684 file_name_extension(_, rdf, File), !,
Chris@0 685 load_rdf(File, Triples).
Chris@0 686 read_triples(File, Triples) :-
Chris@0 687 file_name_extension(_, ttl, File), !,
Chris@0 688 rdf_load_turtle(File, Triples, []).
Chris@0 689
Chris@0 690 %% is_manifest_file(+Path)
Chris@0 691 %
Chris@0 692 % True if Path is the name of a manifest file.
Chris@0 693
Chris@0 694 is_manifest_file(Path) :-
Chris@0 695 file_base_name(Path, File),
Chris@0 696 downcase_atom(File, Lwr),
Chris@0 697 file_name_extension(Base, Ext, Lwr),
Chris@0 698 manifest_file(Base),
Chris@0 699 rdf_extension(Ext), !.
Chris@0 700
Chris@0 701 manifest_file('Manifest').
Chris@0 702 manifest_file('manifest').
Chris@0 703
Chris@0 704 rdf_extension(ttl).
Chris@0 705 rdf_extension(rdf).
Chris@0 706
Chris@0 707
Chris@0 708 %% assert_ontology(+Manifest, +Term:library(Name, File, Facets)) is det.
Chris@0 709 %
Chris@0 710 % Add ontology to our library.
Chris@0 711 %
Chris@0 712 % @tbd Proper behaviour of re-definition?
Chris@0 713
Chris@0 714 assert_ontology(Manifest, Term) :-
Chris@0 715 Term = library(Name, URL, Facets),
Chris@0 716 ( library(Name, _URL2, Facets2)
Chris@0 717 -> memberchk(manifest(Manifest2), Facets2),
Chris@0 718 print_message(warning, rdf(redefined(Manifest, Name, Manifest2)))
Chris@0 719 ; true
Chris@0 720 ),
Chris@0 721 assert(library_db(Name, URL,
Chris@0 722 [ manifest(Manifest)
Chris@0 723 | Facets
Chris@0 724 ])).
Chris@0 725
Chris@0 726
Chris@0 727 %% library(?Id, ?URL, ?Facets)
Chris@0 728 %
Chris@0 729 % Access DB for library information.
Chris@0 730
Chris@0 731 library(Id, URL, Facets) :-
Chris@0 732 nonvar(URL),
Chris@0 733 canonical_url(URL, CanonicalURL),
Chris@0 734 library_db(Id, CanonicalURL, Facets).
Chris@0 735 library(Id, URL, Facets) :-
Chris@0 736 library_db(Id, URL, Facets).
Chris@0 737
Chris@0 738 %% canonical_url(+URL, -CanonicalURL) is det.
Chris@0 739 %
Chris@0 740 % Translate a URL into a canonical form. Currently deals with
Chris@0 741 % file:// urls to take care of filesystem properies such as being
Chris@0 742 % case insensitive and symbolic names.
Chris@0 743 %
Chris@0 744 % @tbd Generic URL handling should also strip ../, etc.
Chris@0 745
Chris@0 746 canonical_url(FileURL, URL) :-
Chris@0 747 file_name_to_url(File, FileURL), !,
Chris@0 748 absolute_file_name(File, Abs),
Chris@0 749 file_name_to_url(Abs, URL).
Chris@0 750 canonical_url(URL, URL).
Chris@0 751
Chris@0 752 %% define_namespace(NS:ns(Mnemonic, Namespace)) is det.
Chris@0 753 %
Chris@0 754 % Add namespace declaration for Mnemonic.
Chris@0 755
Chris@0 756 define_namespace(ns(Mnemonic, Namespace)) :-
Chris@0 757 debug(rdf_library, 'Adding NS ~w = ~q', [Mnemonic, Namespace]),
Chris@0 758 rdf_register_ns(Mnemonic, Namespace,
Chris@0 759 [
Chris@0 760 ]).
Chris@0 761
Chris@0 762
Chris@0 763 /*******************************
Chris@0 764 * MESSAGES *
Chris@0 765 *******************************/
Chris@0 766
Chris@0 767 :- multifile
Chris@0 768 prolog:message/3.
Chris@0 769
Chris@0 770 prolog:message(rdf(no_manifest(Path))) -->
Chris@0 771 [ 'Directory ~w has no Manifest.{ttl,rdf} file'-[Path] ].
Chris@0 772 prolog:message(rdf(redefined(Manifest, Name, Manifest2))) -->
Chris@0 773 [ '~w: Ontology ~w already defined in ~w'-
Chris@0 774 [Manifest, Name, Manifest2]
Chris@0 775 ].
Chris@0 776 prolog:message(rdf(manifest(loaded, Manifest))) -->
Chris@0 777 [ 'Loaded RDF manifest ~w'-[Manifest]
Chris@0 778 ].
Chris@0 779 prolog:message(rdf(load_conflict(C1, C2))) -->
Chris@0 780 [ 'Conflicting loads: ~p <-> ~p'-[C1, C2] ].
Chris@0 781 prolog:message(rdf(loading(Files, Threads))) -->
Chris@0 782 [ 'Loading ~D files using ~D threads ...'-[Files, Threads] ].
Chris@0 783