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
|