790 lines
22 KiB
Perl
790 lines
22 KiB
Perl
|
/* $Id$
|
||
|
|
||
|
Part of SWI-Prolog
|
||
|
|
||
|
Author: Jan Wielemaker
|
||
|
E-mail: wielemak@science.uva.nl
|
||
|
WWW: http://www.swi-prolog.org
|
||
|
Copyright (C): 2007, University of Amsterdam
|
||
|
|
||
|
This program is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU General Public License
|
||
|
as published by the Free Software Foundation; either version 2
|
||
|
of the License, or (at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU Lesser General Public
|
||
|
License along with this library; if not, write to the Free Software
|
||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
|
||
|
As a special exception, if you link this library with other files,
|
||
|
compiled with a Free Software compiler, to produce an executable, this
|
||
|
library does not by itself cause the resulting executable to be covered
|
||
|
by the GNU General Public License. This exception does not however
|
||
|
invalidate any other reasons why the executable file might be covered by
|
||
|
the GNU General Public License.
|
||
|
*/
|
||
|
|
||
|
:- module(rdf_library,
|
||
|
[ rdf_attach_library/1, % +Dir
|
||
|
rdf_load_library/1, % +Ontology
|
||
|
rdf_load_library/2, % +Ontology, +Options
|
||
|
rdf_list_library/0,
|
||
|
rdf_list_library/1, % +Ontology
|
||
|
rdf_list_library/2, % +Ontology, +Options
|
||
|
rdf_library_index/2 % ?Id, ?Facet
|
||
|
]).
|
||
|
:- use_module(library('semweb/rdf_db')).
|
||
|
:- use_module(library('semweb/rdf_turtle')).
|
||
|
:- use_module(library(rdf)).
|
||
|
:- use_module(library(lists)).
|
||
|
:- use_module(library(option)).
|
||
|
:- use_module(library(debug)).
|
||
|
:- use_module(library(error)).
|
||
|
:- use_module(library(pairs)).
|
||
|
:- use_module(library(date)).
|
||
|
:- use_module(library(uri)).
|
||
|
:- use_module(library(http/http_open)).
|
||
|
:- use_module(library(thread)).
|
||
|
|
||
|
/** <module> RDF Library Manager
|
||
|
|
||
|
This module manages an ontology library. Such a library consists of a
|
||
|
directory with manifest files named =manifest.rdf= or =manifest.ttl=
|
||
|
(Turtle). The manifest files define ontologies appearing in the library
|
||
|
as well as namespace mnemonics and dependencies.
|
||
|
|
||
|
The typical usage scenario is
|
||
|
|
||
|
==
|
||
|
?- rdf_attach_library('/some/directory').
|
||
|
?- rdf_load_library(my_ontology).
|
||
|
==
|
||
|
|
||
|
@tbd Add caching info
|
||
|
@author Jan Wielemaker
|
||
|
*/
|
||
|
|
||
|
:- rdf_register_ns(lib, 'http://www.swi-prolog.org/rdf/library/').
|
||
|
|
||
|
:- dynamic
|
||
|
manifest/2, % Path, Time
|
||
|
library_db/3. % Name, URL, Facets
|
||
|
|
||
|
% Force compile-time namespace expansion
|
||
|
|
||
|
:- rdf_meta
|
||
|
edge(+, r,r,o).
|
||
|
|
||
|
/*******************************
|
||
|
* LOADING *
|
||
|
*******************************/
|
||
|
|
||
|
%% rdf_load_library(+Id) is det.
|
||
|
%% rdf_load_library(+Id, +Options) is det.
|
||
|
%
|
||
|
% Load ontologies from the library. A library must first be
|
||
|
% attached using rdf_attach_library/1. Defined Options are:
|
||
|
%
|
||
|
% * import(Bool)
|
||
|
% If =true= (default), also load ontologies that are
|
||
|
% explicitely imported.
|
||
|
%
|
||
|
% * base_uri(URI)
|
||
|
% BaseURI used for loading RDF. Local definitions in
|
||
|
% ontologies overrule this option.
|
||
|
%
|
||
|
% * claimed_source(URL)
|
||
|
% URL from which we claim to have loaded the data.
|
||
|
%
|
||
|
% * not_found(+Level)
|
||
|
% The system does a pre-check for the existence of
|
||
|
% all references RDF databases. If Level is =error=
|
||
|
% it reports missing databases as an error and fails.
|
||
|
% If =warning= it prints them, but continues. If
|
||
|
% =silent=, no checks are preformed. Default is =error=.
|
||
|
%
|
||
|
% * concurrent(Threads)
|
||
|
% Perform the load concurrently using N threads. If not
|
||
|
% specified, the number is determined by
|
||
|
% guess_concurrency/2.
|
||
|
%
|
||
|
% * load(+Bool)
|
||
|
% If =false=, to all the preparation, but do not execute
|
||
|
% the actual loading. See also rdf_list_library/2.
|
||
|
|
||
|
rdf_load_library(Id) :-
|
||
|
rdf_load_library(Id, []).
|
||
|
|
||
|
rdf_load_library(Id, Options) :-
|
||
|
load_commands(Id, Options, Pairs),
|
||
|
pairs_values(Pairs, Commands),
|
||
|
list_to_set(Commands, Cmds2),
|
||
|
delete_virtual(Cmds2, Cmds3),
|
||
|
find_conflicts(Cmds3),
|
||
|
check_existence(Cmds3, Cmds, Options),
|
||
|
( option(concurrent(Threads), Options)
|
||
|
-> true
|
||
|
; guess_concurrency(Cmds, Threads)
|
||
|
),
|
||
|
length(Cmds, NSources),
|
||
|
print_message(informational, rdf(loading(NSources, Threads))),
|
||
|
( option(load(true), Options, true)
|
||
|
-> concurrent(Threads, Cmds, [])
|
||
|
; true
|
||
|
).
|
||
|
|
||
|
delete_virtual([], []).
|
||
|
delete_virtual([virtual(_)|T0], T) :- !,
|
||
|
delete_virtual(T0, T).
|
||
|
delete_virtual([H|T0], [H|T]) :-
|
||
|
delete_virtual(T0, T).
|
||
|
|
||
|
|
||
|
%% find_conflicts(+LoadCommands) is semidet.
|
||
|
%
|
||
|
% Find possibly conflicting options for loading the same source
|
||
|
|
||
|
find_conflicts(Commands) :-
|
||
|
sort(Commands, Cmds),
|
||
|
conflicts(Cmds, Conflicts),
|
||
|
report_conflics(Conflicts),
|
||
|
Conflicts == [].
|
||
|
|
||
|
conflicts([], []).
|
||
|
conflicts([C1, C2|T0], [C1-C2|T]) :-
|
||
|
conflict(C1, C2), !,
|
||
|
conflicts([C2|T0], T).
|
||
|
conflicts([_|T0], T) :-
|
||
|
conflicts(T0, T).
|
||
|
|
||
|
conflict(rdf_load(Src, Options1), rdf_load(Src, Options2)) :-
|
||
|
sort(Options1, S1),
|
||
|
sort(Options2, S2),
|
||
|
S1 \== S2.
|
||
|
|
||
|
report_conflics([]).
|
||
|
report_conflics([C1-C2|T]) :-
|
||
|
print_message(warning, rdf(load_conflict(C1,C2))),
|
||
|
report_conflics(T).
|
||
|
|
||
|
|
||
|
%% check_existence(+CommandsIn, -Commands, +Options) is det.
|
||
|
%
|
||
|
% Report existence errors. Fail if at least one source does not
|
||
|
% exist. and the not_found level is not =silent=.
|
||
|
%
|
||
|
% @error existence_error(urls, ListOfUrls)
|
||
|
|
||
|
check_existence(CommandsIn, Commands, Options) :-
|
||
|
option(not_found(Level), Options, error),
|
||
|
must_be(oneof([error,warning,silent]), Level),
|
||
|
( Level == silent
|
||
|
-> true
|
||
|
; missing_urls(CommandsIn, Commands, Missing),
|
||
|
( Missing == []
|
||
|
-> true
|
||
|
; Level == warning
|
||
|
-> report_missing(Missing, Level)
|
||
|
; existence_error(urls, Missing)
|
||
|
)
|
||
|
).
|
||
|
|
||
|
|
||
|
missing_urls([], [], []).
|
||
|
missing_urls([H|T0], Cmds, Missing) :-
|
||
|
H = rdf_load(URL, _),
|
||
|
( catch(exists_url(URL), error(existence_error(_,_), _), fail)
|
||
|
-> Cmds = [H|T],
|
||
|
missing_urls(T0, T, Missing)
|
||
|
; Missing = [URL|T],
|
||
|
missing_urls(T0, Cmds, T)
|
||
|
).
|
||
|
|
||
|
report_missing([], _).
|
||
|
report_missing([H|T], Level) :-
|
||
|
print_message(Level, error(existence_error(url, H), _)),
|
||
|
report_missing(T, Level).
|
||
|
|
||
|
%% guess_concurrency(+Commands, -Threads) is det.
|
||
|
%
|
||
|
% How much concurrency to use? Set to the number of CPUs if all
|
||
|
% input comes from files or 5 if network based loading is
|
||
|
% demanded.
|
||
|
|
||
|
guess_concurrency(Commands, Threads) :-
|
||
|
count_uris(Commands, FileURLs, OtherURLs),
|
||
|
( FileURLs > 0
|
||
|
-> ( current_prolog_flag(cpu_count, CPUs)
|
||
|
-> true
|
||
|
; CPUs = 1
|
||
|
),
|
||
|
FileThreads is min(FileURLs, CPUs)
|
||
|
; FileThreads = 0
|
||
|
),
|
||
|
( OtherURLs > 0
|
||
|
-> OtherThreads is min(5, OtherURLs)
|
||
|
; OtherThreads = 0
|
||
|
),
|
||
|
Threads is FileThreads + OtherThreads.
|
||
|
|
||
|
count_uris([], 0, 0).
|
||
|
count_uris([rdf_load(URL, _)|T], F, NF) :-
|
||
|
count_uris(T, F0, NF0),
|
||
|
( sub_atom(URL, 0, _, _, 'file://')
|
||
|
-> F is F0 + 1,
|
||
|
NF = NF0
|
||
|
; NF is NF0 + 1,
|
||
|
F = F0
|
||
|
).
|
||
|
|
||
|
|
||
|
%% load_commands(+Id, +Options, -Pairs:list(Level-Command)) is det.
|
||
|
%
|
||
|
% Commands are the RDF commands to execute for rdf_load_library/2.
|
||
|
% Splitting in command collection and execution allows for
|
||
|
% concurrent execution as well as forward checking of possible
|
||
|
% problems.
|
||
|
%
|
||
|
% @tbd Fix poor style; avoid assert/retract.
|
||
|
|
||
|
:- thread_local
|
||
|
command/2.
|
||
|
|
||
|
load_commands(Id, Options, Commands) :-
|
||
|
retractall(command(_,_)),
|
||
|
rdf_update_library_index,
|
||
|
dry_load(Id, 1, Options),
|
||
|
findall(Level-Cmd, retract(command(Level, Cmd)), Commands).
|
||
|
|
||
|
dry_load(Id, Level, Options) :-
|
||
|
( library(Id, File, Facets)
|
||
|
-> merge_base_uri(Facets, Options, Options1),
|
||
|
merge_source(Facets, Options1, Options2),
|
||
|
merge_blanks(Facets, Options2, Options3),
|
||
|
( \+ memberchk(virtual, Facets)
|
||
|
-> load_options(Options3, File, RdfOptions),
|
||
|
assert(command(Level, rdf_load(File, RdfOptions)))
|
||
|
; assert(command(Level, virtual(File)))
|
||
|
),
|
||
|
( option(import(true), Options, true)
|
||
|
-> Level1 is Level + 1,
|
||
|
forall(member(imports(_, Import), Facets),
|
||
|
import(Import, Level1, Options3))
|
||
|
; true
|
||
|
)
|
||
|
; existence_error(ontology, Id)
|
||
|
).
|
||
|
|
||
|
merge_base_uri(Facets, Options0, Options) :-
|
||
|
( option(base_uri(Base), Facets)
|
||
|
-> delete(Options0, base_uri(_), Options1),
|
||
|
Options = [base_uri(Base)|Options1]
|
||
|
; Options = Options0
|
||
|
).
|
||
|
|
||
|
merge_source(Facets, Options0, Options) :-
|
||
|
( option(claimed_source(Base), Facets)
|
||
|
-> delete(Options0, claimed_source(_), Options1),
|
||
|
Options = [claimed_source(Base)|Options1]
|
||
|
; Options = Options0
|
||
|
).
|
||
|
|
||
|
merge_blanks(Facets, Options0, Options) :-
|
||
|
( option(blank_nodes(Share), Facets)
|
||
|
-> delete(Options0, blank_nodes(_), Options1),
|
||
|
Options = [blank_nodes(Share)|Options1]
|
||
|
; Options = Options0
|
||
|
).
|
||
|
|
||
|
load_options(Options, File, RDFOptions) :-
|
||
|
findall(O, load_option(Options, File, O), RDFOptions).
|
||
|
|
||
|
load_option(Options, File, db(Source)) :-
|
||
|
option(claimed_source(Source0), Options),
|
||
|
( sub_atom(Source0, _, _, 0, /)
|
||
|
-> file_base_name(File, Base),
|
||
|
atom_concat(Source0, Base, Source)
|
||
|
; atom_concat(Source, #, Source0)
|
||
|
-> true
|
||
|
).
|
||
|
load_option(Options, File, base_uri(BaseURI)) :-
|
||
|
option(base_uri(Base0), Options),
|
||
|
sub_atom(/, _, _, 0, Base0),
|
||
|
atom_concat(Base0, File, BaseURI).
|
||
|
load_option(Options, _File, blank_nodes(Share)) :-
|
||
|
option(blank_nodes(Share), Options).
|
||
|
|
||
|
%% import(+URL, +Level, +Options) is det.
|
||
|
|
||
|
import(Path, Level, Options) :-
|
||
|
( ( library(Id, Path, _)
|
||
|
-> true
|
||
|
; manifest_for_path(Path, Manifest),
|
||
|
catch(exists_url(Manifest), _, fail)
|
||
|
-> process_manifest(Manifest),
|
||
|
library(Id, Path, _)
|
||
|
)
|
||
|
-> dry_load(Id, Level, Options)
|
||
|
; load_options(Options, Path, RdfOptions),
|
||
|
assert(command(Level, rdf_load(Path, RdfOptions)))
|
||
|
).
|
||
|
|
||
|
manifest_for_path(URL, Manifest) :-
|
||
|
file_directory_name(URL, Parent),
|
||
|
manifest_file(Base),
|
||
|
rdf_extension(Ext),
|
||
|
concat_atom([Parent, /, Base, '.', Ext], Manifest).
|
||
|
|
||
|
%% rdf_list_library(+Id) is det.
|
||
|
%% rdf_list_library(+Id, +Options) is det.
|
||
|
%
|
||
|
% Print library dependency tree to the terminal. Options include
|
||
|
% options for rdf_load_library/2 and
|
||
|
%
|
||
|
% * show_source(+Boolean)
|
||
|
% If =true= (default), show location we are loading
|
||
|
%
|
||
|
% * show_graph(+Boolean)
|
||
|
% If =true= (default =false=), show name of graph
|
||
|
%
|
||
|
% * show_virtual(+Boolean)
|
||
|
% If =false= (default =true=), do not show virtual
|
||
|
% repositories.
|
||
|
%
|
||
|
% * indent(Atom)
|
||
|
% Atom repeated for indentation levels
|
||
|
|
||
|
rdf_list_library(Id) :-
|
||
|
rdf_list_library(Id, []).
|
||
|
rdf_list_library(Id, Options) :-
|
||
|
load_commands(Id, Options, Commands),
|
||
|
maplist(print_load(Options), Commands).
|
||
|
|
||
|
print_load(Options, _Level-virtual(_)) :-
|
||
|
option(show_virtual(false), Options), !.
|
||
|
print_load(Options, Level-Command) :-
|
||
|
option(indent(Indent), Options, '. '),
|
||
|
forall(between(2, Level, _), format(Indent)),
|
||
|
print_command(Command, Options),
|
||
|
format('~N').
|
||
|
|
||
|
print_command(virtual(URL), _Options) :-
|
||
|
format('<~w>', [URL]).
|
||
|
print_command(rdf_load(URL), Options) :-
|
||
|
print_command(rdf_load(URL, []), Options).
|
||
|
print_command(rdf_load(URL, RDFOptions), Options) :-
|
||
|
( option(show_source(true), Options, true)
|
||
|
-> format('~w', [URL]),
|
||
|
( option(blank_nodes(noshare), RDFOptions)
|
||
|
-> format(' <not shared>')
|
||
|
; true
|
||
|
),
|
||
|
( exists_url(URL)
|
||
|
-> true
|
||
|
; format(' [NOT FOUND]')
|
||
|
)
|
||
|
; true
|
||
|
),
|
||
|
( option(show_graph(true), Options, false),
|
||
|
option(db(Base), RDFOptions)
|
||
|
-> format('~N\tSource: ~w', [Base])
|
||
|
; true
|
||
|
).
|
||
|
|
||
|
exists_url(URL) :-
|
||
|
rdf_db:rdf_input(URL, Source, _BaseURI),
|
||
|
existing_url_source(Source).
|
||
|
|
||
|
existing_url_source(file(Path)) :- !,
|
||
|
access_file(Path, read).
|
||
|
existing_url_source(url(http, URL)) :- !,
|
||
|
catch(http_open(URL, Stream, [ method(head) ]), _, fail),
|
||
|
close(Stream).
|
||
|
|
||
|
|
||
|
%% rdf_list_library
|
||
|
%
|
||
|
% Prints known RDF library identifiers to current output.
|
||
|
|
||
|
rdf_list_library :-
|
||
|
rdf_update_library_index,
|
||
|
( rdf_library_index(Id, title(Title)),
|
||
|
format('~w ~t~20|~w', [Id, Title]),
|
||
|
( rdf_library_index(Id, version(Version))
|
||
|
-> format(' (version ~w)', [Version])
|
||
|
; true
|
||
|
),
|
||
|
nl,
|
||
|
fail
|
||
|
; true
|
||
|
).
|
||
|
|
||
|
|
||
|
%% rdf_library_index(?Id, ?Facet) is nondet.
|
||
|
%
|
||
|
% Query the content of the library. Defined facets are:
|
||
|
%
|
||
|
% * source(URL)
|
||
|
% Location from which to load the ontology
|
||
|
%
|
||
|
% * title(Atom)
|
||
|
% Title used for the ontology
|
||
|
%
|
||
|
% * comment(Atom)
|
||
|
% Additional comments for the ontology
|
||
|
%
|
||
|
% * version(Atom)
|
||
|
% Version information on the ontology
|
||
|
%
|
||
|
% * imports(Type, URL)
|
||
|
% URLs needed by this ontology. May succeed multiple
|
||
|
% times. Type is one of =ontology=, =schema= or =instances=.
|
||
|
%
|
||
|
% * base_uri(BaseURI)
|
||
|
% Base URI to use when loading documents. If BaseURI
|
||
|
% ends in =|/|=, the actual filename is attached.
|
||
|
%
|
||
|
% * claimed_source(Source)
|
||
|
% URL from which we claim to have loaded the RDF. If
|
||
|
% Source ends in =|/|=, the actual filename is
|
||
|
% attached.
|
||
|
%
|
||
|
% * blank_nodes(Share)
|
||
|
% Defines how equivalent blank nodes are handled, where
|
||
|
% Share is one of =share= or =noshare=. Default is to
|
||
|
% share.
|
||
|
%
|
||
|
% * provides_ns(URL)
|
||
|
% Ontology provides definitions in the namespace URL.
|
||
|
% The formal definition of this is troublesome, but in
|
||
|
% practice it means the ontology has triples whose
|
||
|
% subjects are in the given namespace.
|
||
|
%
|
||
|
% * uses_ns(URL)
|
||
|
% The ontology depends on the given namespace. Normally
|
||
|
% means it contains triples that have predicates or
|
||
|
% objects in the given namespace.
|
||
|
%
|
||
|
% * manifest(Path)
|
||
|
% Manifest file this ontology is defined in
|
||
|
%
|
||
|
% * virtual
|
||
|
% Entry is virtual (cannot be loaded)
|
||
|
|
||
|
rdf_library_index(Id, Facet) :-
|
||
|
library(Id, Path, Facets),
|
||
|
( Facet = source(Path)
|
||
|
; member(Facet, Facets)
|
||
|
).
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* MANIFEST PROCESSING *
|
||
|
*******************************/
|
||
|
|
||
|
%% rdf_attach_library(+Source)
|
||
|
%
|
||
|
% Attach manifest from Source. Source is one of
|
||
|
%
|
||
|
% * URL
|
||
|
% Load single manifest from this URL
|
||
|
% * File
|
||
|
% Load single manifest from this file
|
||
|
% * Directory
|
||
|
% Scan all subdirectories and load all =|Manifest.ttl|= or
|
||
|
% =|Manifest.rdf|= found.
|
||
|
%
|
||
|
% Encountered namespaces are registered using rdf_register_ns/2.
|
||
|
% Encountered ontologies are added to the index. If a manifest was
|
||
|
% already loaded it will be reloaded if the modification time has
|
||
|
% changed.
|
||
|
|
||
|
rdf_attach_library(URL) :-
|
||
|
atom(URL),
|
||
|
uri_is_global(URL),
|
||
|
\+ is_absolute_file_name(URL), !, % avoid interpreting C: as a schema
|
||
|
process_manifest(URL).
|
||
|
rdf_attach_library(File) :-
|
||
|
absolute_file_name(File, Path,
|
||
|
[ extensions([rdf,ttl]),
|
||
|
access(read),
|
||
|
file_errors(fail)
|
||
|
]), !,
|
||
|
process_manifest(Path).
|
||
|
rdf_attach_library(Dir) :-
|
||
|
absolute_file_name(Dir, Path,
|
||
|
[ file_type(directory),
|
||
|
access(read)
|
||
|
]),
|
||
|
attach_dir(Path, []).
|
||
|
|
||
|
|
||
|
%% rdf_update_library_index
|
||
|
%
|
||
|
% Reload all Manifest files.
|
||
|
|
||
|
rdf_update_library_index :-
|
||
|
forall(manifest(Location, _Time),
|
||
|
process_manifest(Location)).
|
||
|
|
||
|
attach_dir(Path, Visited) :-
|
||
|
memberchk(Path, Visited), !.
|
||
|
attach_dir(Path, Visited) :-
|
||
|
atom_concat(Path, '/*', Pattern),
|
||
|
expand_file_name(Pattern, Members),
|
||
|
( member(Manifest, Members),
|
||
|
is_manifest_file(Manifest)
|
||
|
-> process_manifest(Manifest)
|
||
|
; print_message(silent, rdf(no_manifest(Path)))
|
||
|
),
|
||
|
( member(Dir, Members),
|
||
|
exists_directory(Dir),
|
||
|
file_base_name(Dir, Base),
|
||
|
\+ hidden_base(Base),
|
||
|
attach_dir(Dir, [Path|Visited]),
|
||
|
fail ; true
|
||
|
).
|
||
|
|
||
|
hidden_base('CVS').
|
||
|
hidden_base('cvs'). % Windows
|
||
|
|
||
|
%% process_manifest(+Location) is det.
|
||
|
%
|
||
|
% Process a manifest file, registering encountered namespaces and
|
||
|
% creating clauses for library/3. No op if manifest was loaded and
|
||
|
% not changed. Removes old data if the manifest was changed.
|
||
|
%
|
||
|
% @param Location is either a path name or a URL.
|
||
|
|
||
|
process_manifest(Source) :-
|
||
|
( uri_file_name(Source, Manifest0)
|
||
|
-> absolute_file_name(Manifest0, Manifest)
|
||
|
; absolute_file_name(Source, Manifest)
|
||
|
), % Manifest is a canonical filename
|
||
|
source_time(Manifest, MT),
|
||
|
( manifest(Manifest, Time),
|
||
|
( MT =< Time
|
||
|
-> !
|
||
|
; retractall(manifest(Manifest, Time)),
|
||
|
library_db(Id, URL, Facets),
|
||
|
memberchk(manifest(Manifest), Facets),
|
||
|
retractall(library_db(Id, URL, Facets)),
|
||
|
fail
|
||
|
)
|
||
|
; read_triples(Manifest, Triples),
|
||
|
process_triples(Manifest, Triples),
|
||
|
print_message(informational, rdf(manifest(loaded, Manifest))),
|
||
|
assert(manifest(Manifest, MT))
|
||
|
).
|
||
|
|
||
|
process_triples(Manifest, Triples) :-
|
||
|
findall(ns(Mnemonic, NameSpace),
|
||
|
extract_namespace(Triples, Mnemonic, NameSpace),
|
||
|
NameSpaces),
|
||
|
findall(Ontology,
|
||
|
extract_ontology(Triples, Ontology),
|
||
|
Ontologies),
|
||
|
maplist(define_namespace, NameSpaces),
|
||
|
maplist(assert_ontology(Manifest), Ontologies).
|
||
|
|
||
|
%% extract_namespace(+Triples, -Mnemonic, -NameSpace)
|
||
|
%
|
||
|
% True if Mnemonic is an abbreviation of NameSpace.
|
||
|
|
||
|
extract_namespace(Triples, Mnemonic, Namespace) :-
|
||
|
edge(Triples, Decl, lib:mnemonic, literal(Mnemonic)),
|
||
|
edge(Triples, Decl, lib:namespace, Namespace).
|
||
|
|
||
|
%% extract_ontology(+Triples, -Ontology) is nondet.
|
||
|
%
|
||
|
% Extract definition of an ontology
|
||
|
|
||
|
extract_ontology(Triples, library(Name, URL, Options)) :-
|
||
|
edge(Triples, URL, rdf:type, Type),
|
||
|
( ontology_type(Type)
|
||
|
-> file_base_name(URL, BaseName),
|
||
|
file_name_extension(Name, _, BaseName),
|
||
|
findall(Facet, facet(Triples, URL, Facet), Options)
|
||
|
).
|
||
|
|
||
|
ontology_type(X) :-
|
||
|
( rdf_equal(X, lib:'Ontology')
|
||
|
; rdf_equal(X, lib:'Schema')
|
||
|
; rdf_equal(X, lib:'Instances')
|
||
|
).
|
||
|
|
||
|
%% facet(+Triples, +File, -Facet) is nondet.
|
||
|
%
|
||
|
% Enumerate facets about File from Triples. Facets are described
|
||
|
% with rdf_library_index/2.
|
||
|
|
||
|
facet(Triples, File, title(Title)) :-
|
||
|
edge(Triples, File, dc:title, literal(Title)).
|
||
|
:- if(rdf_current_ns(dcterms, _)).
|
||
|
facet(Triples, File, title(Title)) :-
|
||
|
edge(Triples, File, dcterms:title, literal(Title)).
|
||
|
:- endif.
|
||
|
facet(Triples, File, version(Version)) :-
|
||
|
edge(Triples, File, owl:versionInfo, literal(Version)).
|
||
|
facet(Triples, File, comment(Comment)) :-
|
||
|
edge(Triples, File, rdfs:comment, literal(Comment)).
|
||
|
facet(Triples, File, base_uri(BaseURI)) :-
|
||
|
edge(Triples, File, lib:baseURI, BaseURI).
|
||
|
facet(Triples, File, claimed_source(Source)) :-
|
||
|
edge(Triples, File, lib:source, Source).
|
||
|
facet(Triples, File, blank_nodes(Mode)) :-
|
||
|
edge(Triples, File, lib:blankNodes, literal(Mode)),
|
||
|
must_be(oneof([share,noshare]), Mode).
|
||
|
facet(Triples, File, imports(ontology, Path)) :-
|
||
|
edge(Triples, File, owl:imports, Path).
|
||
|
facet(Triples, File, imports(schema, Path)) :-
|
||
|
edge(Triples, File, lib:schema, Path).
|
||
|
facet(Triples, File, imports(instances, Path)) :-
|
||
|
edge(Triples, File, lib:instances, Path).
|
||
|
facet(Triples, File, provides_ns(NS)) :-
|
||
|
edge(Triples, File, lib:providesNamespace, NSDecl),
|
||
|
edge(Triples, NSDecl, lib:namespace, NS).
|
||
|
facet(Triples, File, uses_ns(NS)) :-
|
||
|
edge(Triples, File, lib:usesNamespace, NSDecl),
|
||
|
edge(Triples, NSDecl, lib:namespace, NS).
|
||
|
facet(Triples, File, virtual) :-
|
||
|
edge(Triples, File, rdf:type, lib:'Virtual').
|
||
|
|
||
|
%% edge(+Triples, ?S, ?P, ?O) is nondet.
|
||
|
%
|
||
|
% Like rdf/3 over a list of Triples.
|
||
|
|
||
|
edge(Triples, S, P, O) :-
|
||
|
member(rdf(S,P,O), Triples).
|
||
|
|
||
|
%% source_time(+Source, -Modified) is semidet.
|
||
|
%
|
||
|
% Modified is the last modification time of Source.
|
||
|
%
|
||
|
% @error existence_error(Type, Source).
|
||
|
|
||
|
source_time(URL, Modified) :-
|
||
|
sub_atom(URL, 0, _, _, 'http://'), !,
|
||
|
http_open(URL, Stream,
|
||
|
[ header(last_modified, Date),
|
||
|
method(head)
|
||
|
]),
|
||
|
close(Stream),
|
||
|
Date \== '',
|
||
|
parse_time(Date, Modified).
|
||
|
source_time(URL, Modified) :-
|
||
|
uri_file_name(URL, File), !,
|
||
|
time_file(File, Modified).
|
||
|
source_time(File, Modified) :-
|
||
|
time_file(File, Modified).
|
||
|
|
||
|
|
||
|
%% read_triples(+File, -Triples) is det.
|
||
|
%
|
||
|
% Read RDF/XML or Turtle file into a list of triples.
|
||
|
|
||
|
read_triples(File, Triples) :-
|
||
|
file_name_extension(_, rdf, File), !,
|
||
|
load_rdf(File, Triples).
|
||
|
read_triples(File, Triples) :-
|
||
|
file_name_extension(_, ttl, File), !,
|
||
|
rdf_load_turtle(File, Triples, []).
|
||
|
|
||
|
%% is_manifest_file(+Path)
|
||
|
%
|
||
|
% True if Path is the name of a manifest file.
|
||
|
|
||
|
is_manifest_file(Path) :-
|
||
|
file_base_name(Path, File),
|
||
|
downcase_atom(File, Lwr),
|
||
|
file_name_extension(Base, Ext, Lwr),
|
||
|
manifest_file(Base),
|
||
|
rdf_extension(Ext), !.
|
||
|
|
||
|
manifest_file('Manifest').
|
||
|
manifest_file('manifest').
|
||
|
|
||
|
rdf_extension(ttl).
|
||
|
rdf_extension(rdf).
|
||
|
|
||
|
|
||
|
%% assert_ontology(+Manifest, +Term:library(Name, File, Facets)) is det.
|
||
|
%
|
||
|
% Add ontology to our library.
|
||
|
%
|
||
|
% @tbd Proper behaviour of re-definition?
|
||
|
|
||
|
assert_ontology(Manifest, Term) :-
|
||
|
Term = library(Name, URL, Facets),
|
||
|
( library(Name, _URL2, Facets2)
|
||
|
-> memberchk(manifest(Manifest2), Facets2),
|
||
|
print_message(warning, rdf(redefined(Manifest, Name, Manifest2)))
|
||
|
; true
|
||
|
),
|
||
|
assert(library_db(Name, URL,
|
||
|
[ manifest(Manifest)
|
||
|
| Facets
|
||
|
])).
|
||
|
|
||
|
|
||
|
%% library(?Id, ?URL, ?Facets)
|
||
|
%
|
||
|
% Access DB for library information.
|
||
|
|
||
|
library(Id, URL, Facets) :-
|
||
|
nonvar(URL),
|
||
|
normalize_url(URL, CanonicalURL),
|
||
|
library_db(Id, CanonicalURL, Facets).
|
||
|
library(Id, URL, Facets) :-
|
||
|
library_db(Id, URL, Facets).
|
||
|
|
||
|
%% normalize_url(+URL, -Normalized)
|
||
|
%
|
||
|
% Like uri_normalized/2, but we also need (platform dependent)
|
||
|
% filename canonization.
|
||
|
|
||
|
normalize_url(URL, CanonicalURL) :-
|
||
|
uri_file_name(URL, File), !,
|
||
|
absolute_file_name(File, CanFile),
|
||
|
uri_file_name(CanonicalURL, CanFile).
|
||
|
normalize_url(URL, CanonicalURL) :-
|
||
|
uri_normalized(URL, CanonicalURL).
|
||
|
|
||
|
%% define_namespace(NS:ns(Mnemonic, Namespace)) is det.
|
||
|
%
|
||
|
% Add namespace declaration for Mnemonic.
|
||
|
|
||
|
define_namespace(ns(Mnemonic, Namespace)) :-
|
||
|
debug(rdf_library, 'Adding NS ~w = ~q', [Mnemonic, Namespace]),
|
||
|
rdf_register_ns(Mnemonic, Namespace,
|
||
|
[
|
||
|
]).
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* MESSAGES *
|
||
|
*******************************/
|
||
|
|
||
|
:- multifile
|
||
|
prolog:message/3.
|
||
|
|
||
|
prolog:message(rdf(no_manifest(Path))) -->
|
||
|
[ 'Directory ~w has no Manifest.{ttl,rdf} file'-[Path] ].
|
||
|
prolog:message(rdf(redefined(Manifest, Name, Manifest2))) -->
|
||
|
[ '~w: Ontology ~w already defined in ~w'-
|
||
|
[Manifest, Name, Manifest2]
|
||
|
].
|
||
|
prolog:message(rdf(manifest(loaded, Manifest))) -->
|
||
|
[ 'Loaded RDF manifest ~w'-[Manifest]
|
||
|
].
|
||
|
prolog:message(rdf(load_conflict(C1, C2))) -->
|
||
|
[ 'Conflicting loads: ~p <-> ~p'-[C1, C2] ].
|
||
|
prolog:message(rdf(loading(Files, Threads))) -->
|
||
|
[ 'Loading ~D files using ~D threads ...'-[Files, Threads] ].
|
||
|
|