1166 lines
34 KiB
Prolog
1166 lines
34 KiB
Prolog
/* Part of SWI-Prolog
|
|
|
|
Author: Jan Wielemaker
|
|
E-mail: J.Wielemaker@uva.nl
|
|
WWW: http://www.swi-prolog.org
|
|
Copyright (C): 2009, 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 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_turtle_write,
|
|
[ rdf_save_turtle/2, % +File, +Options
|
|
rdf_save_canonical_turtle/2 % +File, +Options
|
|
]).
|
|
:- use_module(library(semweb/rdf_db)).
|
|
:- use_module(library(semweb/turtle_base)).
|
|
:- use_module(library(option)).
|
|
:- use_module(library(record)).
|
|
:- use_module(library(error)).
|
|
:- use_module(library(lists)).
|
|
:- use_module(library(rbtrees)).
|
|
:- use_module(library(apply)).
|
|
:- use_module(library(url)).
|
|
:- use_module(library(pairs)).
|
|
:- use_module(library(debug)).
|
|
:- use_module(library(sgml_write)).
|
|
|
|
|
|
/** <module> Turtle - Terse RDF Triple Language writer
|
|
|
|
This module implements the Turtle language for representing the RDF
|
|
triple model as defined by Dave Beckett from the Institute for Learning
|
|
and Research Technology University of Bristol in the document:
|
|
|
|
* http://www.w3.org/TeamSubmission/turtle/
|
|
* http://www.w3.org/TeamSubmission/2008/SUBM-turtle-20080114/#sec-conformance
|
|
|
|
The Turtle format is designed as an RDF serialization that is easy to
|
|
read and write by both machines and humans. Due to the latter property,
|
|
this library goes a long way in trying to produce human-readable output.
|
|
|
|
In addition to the human-readable format, this library can write a
|
|
_canonical_ representation of RDF graphs. The canonical representation
|
|
has the following properties:
|
|
|
|
* Equivalent graphs result in the same document. Graphs are
|
|
considered equivalent iff they contain the same _set_ of
|
|
triples, regardless of the labeling of blank nodes in the
|
|
graph.
|
|
|
|
* Changes to the graph are diff-friendly. This means
|
|
|
|
- Prefixes are combined in the header and thus changes
|
|
to the namespaces only result in changes in the header.
|
|
- Blank nodes that are used only once (including collections)
|
|
are written in-line with the object they belong to.
|
|
- For other blank nodes we to realise stable labeling that
|
|
is based on property-values.
|
|
|
|
@tbd Low-level string output takes 28% of the time. Move to C?
|
|
*/
|
|
|
|
:- record
|
|
tw_state(graph, % graph being saved
|
|
base, % The base-URI
|
|
encoding=utf8, % Desired encoding
|
|
indent:nonneg=8, % Indent for ; and ,-lists
|
|
tab_distance:nonneg=8, % Tab distance
|
|
subject_white_lines:nonneg=1,%Extra lines between subjects
|
|
align_prefixes:boolean=true,%Align prefix declarations
|
|
user_prefixes:boolean=true,% Use rdf_current_ns/2?
|
|
comment:boolean=true, % write some comments into the file
|
|
group:boolean=true, % Group using ; and ,
|
|
single_line_bnodes:boolean=false, % No newline after ;
|
|
canonize_numbers:boolean=false, % How to write numbers
|
|
canonical:boolean=false,
|
|
% Private fields
|
|
bnode_id=0, % Incrementing bnode-id
|
|
nodeid_map, % RBTree mapping NodeIDs to Refs
|
|
bnode_hash, % RBTree holding reuse-count of hashes
|
|
subject_count, % # subjects saved
|
|
triple_count=0, % # triples saved
|
|
base_root, % Root URL of base
|
|
base_dir, % Directory
|
|
base_path, % Path of base
|
|
prefix_map). % List of Prefix-Map
|
|
|
|
|
|
%% rdf_save_turtle(+Out, +Options) is det.
|
|
%
|
|
% Save an RDF graph as N3. Options processed are:
|
|
%
|
|
% * align_prefixes(+Boolean)
|
|
% Nicely align the @prefix declarations
|
|
% * base(+Base)
|
|
% Save relative to the given Base
|
|
% * canonize_numbers(+Boolean)
|
|
% If =true= (default =false=), emit numeric datatypes using
|
|
% Prolog's write to achieve canonical output.
|
|
% * comment(+Boolean)
|
|
% It =true= (default), write some informative comments
|
|
% between the output segments
|
|
% * encoding(+Encoding)
|
|
% Encoding used for the output stream. Default is UTF-8.
|
|
% * indent(+Column)
|
|
% Indentation for ; -lists. `0' does not indent, but
|
|
% writes on the same line. Default is 8.
|
|
% * graph(+Graph)
|
|
% Save only the named graph
|
|
% * group(+Boolean)
|
|
% If =true= (default), using P-O and O-grouping.
|
|
% * single_line_bnodes(+Bool)
|
|
% If =true= (default =false=), write [...] and (...) on a
|
|
% single line.
|
|
% * subject_white_lines(+Count)
|
|
% Extra white lines to insert between statements about a
|
|
% different subject. Default is 1.
|
|
% * tab_distance(+Tab)
|
|
% Distance between tab-stops. `0' forces the library to
|
|
% use only spaces for layout. Default is 8.
|
|
% * user_prefixes(+Boolean)
|
|
% If =true= (default), use prefixes from rdf_current_ns/2.
|
|
%
|
|
% @param Out is one of stream(Stream), a stream handle, a file-URL
|
|
% or an atom that denotes a filename.
|
|
|
|
rdf_save_turtle(Spec, Options) :-
|
|
thread_self(Me),
|
|
thread_statistics(Me, cputime, T0),
|
|
must_be(list, Options),
|
|
make_tw_state(Options, State0, _Rest),
|
|
init_base(State0, State1),
|
|
init_prefix_map(State1, State),
|
|
tw_state_encoding(State, Enc),
|
|
open_output(Spec, Enc, Stream, Cleanup),
|
|
call_cleanup(tw_graph(State, Stream),
|
|
Cleanup),
|
|
thread_statistics(Me, cputime, T1),
|
|
Time is T1-T0,
|
|
tw_state_triple_count(State, SavedTriples),
|
|
tw_state_subject_count(State, SavedSubjects),
|
|
print_message(informational,
|
|
rdf(saved(Spec, Time, SavedSubjects, SavedTriples))).
|
|
|
|
|
|
%% rdf_save_canonical_turtle(+Spec, +Options) is det.
|
|
%
|
|
% Save triples in a canonical format. This is the same as
|
|
% rdf_save_turtle/3, but using different defaults. In particular:
|
|
%
|
|
% * encoding(utf8),
|
|
% * indent(0),
|
|
% * tab_distance(0),
|
|
% * subject_white_lines(1),
|
|
% * align_prefixes(false),
|
|
% * user_prefixes(false)
|
|
% * comment(false),
|
|
% * group(false),
|
|
% * single_line_bnodes(true)
|
|
%
|
|
% @tbd Work in progress. Notably blank-node handling is
|
|
% incomplete.
|
|
|
|
rdf_save_canonical_turtle(Spec, Options) :-
|
|
rdf_save_turtle(Spec,
|
|
[ encoding(utf8),
|
|
indent(0),
|
|
tab_distance(0),
|
|
subject_white_lines(1),
|
|
align_prefixes(false),
|
|
user_prefixes(false),
|
|
comment(false),
|
|
group(false),
|
|
single_line_bnodes(true),
|
|
canonical(true)
|
|
| Options
|
|
]).
|
|
|
|
%% open_output(+Spec, +Encoding, -Stream, -Cleanup) is det.
|
|
%
|
|
% Open output Spec, returning a stream using Encoding.
|
|
%
|
|
% @param Cleanup is a goal that must be used to revert the side
|
|
% effects of open_output/4.
|
|
|
|
open_output(stream(Out), Encoding, Out,
|
|
set_stream(Out, encoding(Old))) :- !,
|
|
stream_property(Out, encoding(Old)),
|
|
set_stream(Out, encoding(Encoding)).
|
|
open_output(Stream, Encoding, Out, Cleanup) :-
|
|
\+ atom(Stream),
|
|
is_stream(Stream), !,
|
|
open_output(stream(Stream), Encoding, Out, Cleanup).
|
|
open_output(Spec, Encoding, Out,
|
|
close(Out)) :-
|
|
out_to_file(Spec, File),
|
|
open(File, write, Out, [encoding(Encoding)]).
|
|
|
|
out_to_file(URL, File) :-
|
|
atom(URL),
|
|
file_name_to_url(File, URL), !.
|
|
out_to_file(File, File).
|
|
|
|
|
|
/*******************************
|
|
* PREFIXES *
|
|
*******************************/
|
|
|
|
%% init_prefix_map(+State, -State) is det.
|
|
%
|
|
% Set the prefix_map of State. The prefix map is list of
|
|
% Prefix-URI of prefixes to use for emitting the graph requested
|
|
% in State. If multiple prefixes are present where the one is a
|
|
% prefix of the other, the longer one appears first in the list.
|
|
|
|
init_prefix_map(State0, State) :-
|
|
tw_state_graph(State0, Graph),
|
|
rdf_graph_prefixes(Graph, Prefixes, turtle_prefix),
|
|
remove_base(State0, Prefixes, Prefixes2),
|
|
prefix_names(Prefixes2, State0, Pairs),
|
|
transpose_pairs(Pairs, URI_Abrevs),
|
|
reverse(URI_Abrevs, RURI_Abrevs),
|
|
flip_pairs(RURI_Abrevs, PrefixMap),
|
|
set_prefix_map_of_tw_state(PrefixMap, State0, State).
|
|
|
|
%% turtle_prefix(+Where, +Prefix, +URI) is semidet.
|
|
%
|
|
% Test whether we want to include the proposed prefix in the
|
|
% @prefix declaration.
|
|
|
|
turtle_prefix(_, Prefix, URI) :-
|
|
sub_atom(Prefix, _, 1, 0, Last),
|
|
turtle_prefix_char(Last),
|
|
atom_concat(Prefix, Local, URI),
|
|
\+ sub_atom(Local, _, _, _, '.').
|
|
|
|
turtle_prefix_char('#').
|
|
turtle_prefix_char('/').
|
|
|
|
|
|
remove_base(State, Prefixes, PrefixesNoBase) :-
|
|
tw_state_base_dir(State, BaseDir),
|
|
atom(BaseDir), !,
|
|
delete(Prefixes, BaseDir, PrefixesNoBase).
|
|
remove_base(_State, Prefixes, Prefixes).
|
|
|
|
flip_pairs([], []).
|
|
flip_pairs([Key-Val|Pairs], [Val-Key|Flipped]) :-
|
|
flip_pairs(Pairs, Flipped).
|
|
|
|
prefix_names(URIs, State, Prefixes) :-
|
|
prefix_names(URIs, State, 1, Prefixes, []).
|
|
|
|
prefix_names([], _, _, List, List) :- !.
|
|
prefix_names(URIs, State, Len, Prefixes, Tail) :-
|
|
prefix_names(URIs, State, Len, Prefixes, PTail, Rest),
|
|
Len1 is Len + 1,
|
|
prefix_names(Rest, State, Len1, PTail, Tail).
|
|
|
|
prefix_names(URIs, State, Len, Prefixes, PTail, Rest) :-
|
|
map_list_to_pairs(propose_abbrev(State, Len), URIs, Pairs), !,
|
|
keysort(Pairs, Sorted),
|
|
unique(Sorted, Prefixes, PTail, Rest).
|
|
prefix_names(URIs, _, _, Prefixes, PTail, []) :-
|
|
number_prefixes(URIs, 1, Prefixes, PTail).
|
|
|
|
number_prefixes([], _, PL, PL).
|
|
number_prefixes([H|T0], N, [P-H|PL], T) :-
|
|
atomic_concat(ns, N, P),
|
|
succ(N, N1),
|
|
number_prefixes(T0, N1, PL, T).
|
|
|
|
unique([], L, L, []).
|
|
unique([A-U|T0], [A-U|T], L, Rest) :-
|
|
T0 \= [A-_|_], !,
|
|
unique(T0, T, L, Rest).
|
|
unique([A-U|T0], Prefixes, L, [U|Rest0]) :-
|
|
strip_keys(T0, A, T1, Rest0, Rest),
|
|
unique(T1, Prefixes, L, Rest).
|
|
|
|
strip_keys([A-U|T0], A, T, [U|R0], R) :- !,
|
|
strip_keys(T0, A, T, R0, R).
|
|
strip_keys(L, _, L, R, R).
|
|
|
|
|
|
%% propose_abbrev(+State, +Len, +URI, -Abbrev) is multi.
|
|
%
|
|
% Propose an abbreviation for URI. Backtracking yields longer
|
|
% ones.
|
|
|
|
propose_abbrev(_, _, URI, Abbrev) :-
|
|
well_known_ns(Abbrev, URI), !.
|
|
propose_abbrev(State, _, URI, Abbrev) :-
|
|
tw_state_user_prefixes(State, true),
|
|
rdf_current_ns(Abbrev, URI), !.
|
|
propose_abbrev(_, Len, URI, Abbrev) :-
|
|
namespace_parts(URI, Parts),
|
|
include(abbrev_part, Parts, Names),
|
|
reverse(Names, RevNames),
|
|
length(Use, Len),
|
|
append(Use, _, RevNames),
|
|
atomic_list_concat(Use, -, Abbrev).
|
|
|
|
abbrev_part(X) :-
|
|
turtle_name(X),
|
|
\+ well_known_ns(X, _),
|
|
\+ well_known_extension(X).
|
|
|
|
well_known_ns(rdf, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#').
|
|
well_known_ns(rdfs, 'http://www.w3.org/2000/01/rdf-schema#').
|
|
well_known_ns(owl, 'http://www.w3.org/2002/07/owl#').
|
|
well_known_ns(xsd, 'http://www.w3.org/2001/XMLSchema#').
|
|
well_known_ns(dc, 'http://purl.org/dc/elements/1.1/').
|
|
|
|
well_known_extension(ttl).
|
|
well_known_extension(nt).
|
|
well_known_extension(n3).
|
|
well_known_extension(xml).
|
|
well_known_extension(rdf).
|
|
well_known_extension(owl).
|
|
|
|
%% namespace_parts(+URL, -Parts)
|
|
|
|
namespace_parts(URL, Parts) :-
|
|
atom_codes(URL, Codes),
|
|
phrase(parts(Parts), Codes), !.
|
|
namespace_parts(URL, _) :-
|
|
format(user_error, 'Couldn\'t split ~q~n', [URL]),
|
|
fail.
|
|
|
|
parts(List) --> sep2, parts2(List).
|
|
|
|
parts2([H|T]) -->
|
|
string(Codes), {Codes \== []},
|
|
sep, !,
|
|
{atom_codes(H, Codes)},
|
|
parts2(T).
|
|
parts2([]) --> [].
|
|
|
|
string([]) --> [].
|
|
string([H|T]) --> [H], string(T).
|
|
|
|
sep --> sep_char, sep2.
|
|
sep([], []).
|
|
|
|
sep2 --> sep_char, !, sep2.
|
|
sep2 --> [].
|
|
|
|
sep_char --> "/".
|
|
sep_char --> ":".
|
|
sep_char --> ".".
|
|
sep_char --> "?".
|
|
sep_char --> "#".
|
|
|
|
|
|
%% init_base(+State0, -State) is det.
|
|
%
|
|
% Initialise dealing with the base URI. It sets two attributes of
|
|
% the state: base_root and base_path.
|
|
|
|
init_base(State0, State) :-
|
|
tw_state_base(State0, BaseURI),
|
|
atom(BaseURI), !,
|
|
parse_url(BaseURI, Attributes),
|
|
include(root_part, Attributes, RootAttrs),
|
|
parse_url(BaseRoot, RootAttrs),
|
|
memberchk(path(BasePath), Attributes),
|
|
file_directory_name(BasePath, BaseDir),
|
|
atomic_list_concat([BaseRoot, BaseDir, /], BaseDirURI),
|
|
set_base_root_of_tw_state(BaseRoot, State0, State1),
|
|
set_base_path_of_tw_state(BasePath, State1, State2),
|
|
set_base_dir_of_tw_state(BaseDirURI, State2, State).
|
|
init_base(State, State).
|
|
|
|
root_part(protocol(_)).
|
|
root_part(host(_)).
|
|
root_part(port(_)).
|
|
|
|
|
|
/*******************************
|
|
* SAVE *
|
|
*******************************/
|
|
|
|
%% tw_graph(+State, +Out) is det.
|
|
%
|
|
% Write an RDF graph as Turtle data.
|
|
%
|
|
% @tbd Write unconnected and multi-connected blank-nodes.
|
|
|
|
tw_graph(State, Out) :-
|
|
tw_state_prefix_map(State, PrefixMap),
|
|
tw_prefix_map(PrefixMap, State, Out),
|
|
subjects(State, Subjects),
|
|
length(Subjects, SubjectCount),
|
|
tw_state_subject_count(State, SubjectCount),
|
|
partition(rdf_is_bnode, Subjects, BNodes, ProperSubjects),
|
|
maplist(pair_var, BNodes, Pairs),
|
|
ord_list_to_rbtree(Pairs, BNTree),
|
|
tw_state_nodeid_map(State, BNTree),
|
|
( ProperSubjects == []
|
|
-> true
|
|
; length(ProperSubjects, PSCount),
|
|
comment(State, 'Named toplevel resources (~D)', [PSCount], Out),
|
|
tw_proper_subjects(ProperSubjects, State, Out)
|
|
),
|
|
tw_bnodes(Pairs, State, Out).
|
|
|
|
pair_var(BNode, BNode-_).
|
|
|
|
|
|
%% tw_prefix_map(+PrefixMap, +State, +Out) is det.
|
|
%
|
|
% Write the @base and @prefix declarations
|
|
|
|
tw_prefix_map(PrefixMap, State, Out) :-
|
|
tw_state_align_prefixes(State, true), !,
|
|
longest_prefix(PrefixMap, 0, Length),
|
|
PrefixCol is Length+10,
|
|
tw_base(PrefixCol, State, Out),
|
|
tw_prefix_map(PrefixMap, PrefixCol, State, Out).
|
|
tw_prefix_map(PrefixMap, State, Out) :-
|
|
tw_base(0, State, Out),
|
|
tw_prefix_map(PrefixMap, 0, State, Out).
|
|
|
|
longest_prefix([], L, L).
|
|
longest_prefix([Prefix-_|T], L0, L) :-
|
|
atom_length(Prefix, L1),
|
|
L2 is max(L0, L1),
|
|
longest_prefix(T, L2, L).
|
|
|
|
|
|
tw_base(Col, State, Out) :-
|
|
tw_state_base(State, Base),
|
|
atom(Base), !,
|
|
format(Out, '@base ~t~*|', [Col]),
|
|
turtle_write_uri(Out, Base),
|
|
format(Out, ' .~n', []).
|
|
tw_base(_, _, _).
|
|
|
|
|
|
tw_prefix_map([], _, _, _).
|
|
tw_prefix_map([Prefix-URI|T], Col, State, Out) :-
|
|
format(Out, '@prefix ~t~w: ~*|', [Prefix, Col]),
|
|
tw_relative_uri(URI, State, Out),
|
|
format(Out, ' .~n', []),
|
|
( T == []
|
|
-> true
|
|
; tw_prefix_map(T, Col, State, Out)
|
|
).
|
|
|
|
|
|
%% tw_proper_subjects(+Subjects, +State, +Out) is det.
|
|
%
|
|
% Write the subjects that are not Bnodes.
|
|
|
|
tw_proper_subjects([], _, _).
|
|
tw_proper_subjects([H|T], State, Out) :-
|
|
separate_subjects(State, Out),
|
|
tw_subject(H, H, State, Out),
|
|
tw_proper_subjects(T, State, Out).
|
|
|
|
|
|
separate_subjects(State, Out) :-
|
|
tw_state_subject_white_lines(State, ExtraLines),
|
|
put_n(ExtraLines, '\n', Out).
|
|
|
|
%% tw_subject(+URI, +State, +Out) is det.
|
|
%
|
|
% Write a toplevel non-bnode subject.
|
|
|
|
tw_subject(URI, Ref, State, Out) :-
|
|
subject_triples(URI, State, Pairs),
|
|
length(Pairs, Count),
|
|
inc_triple_count(State, Count),
|
|
group_po(Pairs, Grouped),
|
|
tw_subject_triples(Grouped, Ref, State, Out).
|
|
|
|
group_po(Pairs, Grouped) :-
|
|
group_pairs_by_key(Pairs, Grouped0),
|
|
rdf_equal(rdf:type, RDFType),
|
|
( select(RDFType-Types, Grouped0, Grouped1)
|
|
-> Grouped = [RDFType-Types|Grouped1]
|
|
; Grouped = Grouped0
|
|
).
|
|
|
|
%% tw_bnodes(+Pairs, +State, +Out) is det.
|
|
%
|
|
% Write the Bnodes. Pairs is a list URI-Ref, where Ref is one of
|
|
% =written= if the Bnode is already written; an integer if it is
|
|
% used multiple times or a variable if it has not been written.
|
|
% The order in which we deal with bnodes is defined as follows:
|
|
%
|
|
% * First, write the bnodes that are not referenced at all
|
|
% as toplevel bnodes using [ ... ] notation.
|
|
%
|
|
% * Next, write the bnodes that need written as toplevel
|
|
% nodes using the _:XX notation because they are referenced
|
|
% multiple times in the graph. Continue this process until it
|
|
% is exhausted.
|
|
|
|
tw_bnodes(Pairs, State, Out) :-
|
|
tw_top_bnodes(Pairs, State, Out, Rest1),
|
|
tw_numbered_bnodes(Rest1, State, Out, 1, Rest2),
|
|
tw_cyclic_bnodes(Rest2, State, Out, 0).
|
|
|
|
|
|
tw_numbered_bnodes([], _, _, _, []) :- !.
|
|
tw_numbered_bnodes(Pairs, State, Out, Level, Rest) :-
|
|
multi_referenced(Pairs, RefPairs, Rest0),
|
|
( RefPairs == []
|
|
-> Rest = Rest0
|
|
; length(RefPairs, Count),
|
|
comment(State, 'Level ~D multi-referenced blank-nodes (~D)',
|
|
[ Level, Count ], Out),
|
|
tw_ref_bnodes(RefPairs, State, Out),
|
|
Level1 is Level + 1,
|
|
tw_numbered_bnodes(Rest0, State, Out, Level1, Rest)
|
|
).
|
|
|
|
multi_referenced([], [], []).
|
|
multi_referenced([H|T], RefPairs, Rest) :-
|
|
H = _-Ref,
|
|
( Ref == written
|
|
-> multi_referenced(T, RefPairs, Rest)
|
|
; var(Ref)
|
|
-> Rest = [H|TR],
|
|
multi_referenced(T, RefPairs, TR)
|
|
; assertion(Ref = bnode(_)),
|
|
RefPairs = [H|TRP], % assigned reference
|
|
multi_referenced(T, TRP, Rest)
|
|
).
|
|
|
|
tw_ref_bnodes([], _, _).
|
|
tw_ref_bnodes([BNode-Ref|T], State, Out) :-
|
|
separate_subjects(State, Out),
|
|
tw_subject(BNode, Ref, State, Out),
|
|
tw_ref_bnodes(T, State, Out).
|
|
|
|
|
|
%% tw_top_bnodes(+Pairs, +State, +Out, -Rest)
|
|
%
|
|
% Write the top bnodes: those that do not appear as an object
|
|
% anywhere.
|
|
|
|
tw_top_bnodes(Pairs, State, Out, Rest) :-
|
|
unreferenced(Pairs, State, TopBNodes, Rest),
|
|
( TopBNodes == []
|
|
-> true
|
|
; length(TopBNodes, Count),
|
|
comment(State, 'Toplevel blank-nodes (~D)', [Count], Out),
|
|
sort_bnodes(TopBNodes, SortedTopBNodes, State),
|
|
tw_top_bnodes(SortedTopBNodes, State, Out)
|
|
).
|
|
|
|
unreferenced([], _, [], []).
|
|
unreferenced([H|T], State, UnrefPairs, Rest) :-
|
|
H = BNode-Ref,
|
|
( Ref == written
|
|
-> unreferenced(T, State, UnrefPairs, Rest)
|
|
; var(Ref),
|
|
object_link_count(BNode, State, 0)
|
|
-> UnrefPairs = [H|URT],
|
|
unreferenced(T, State, URT, Rest)
|
|
; Rest = [H|TR],
|
|
unreferenced(T, State, UnrefPairs, TR)
|
|
).
|
|
|
|
tw_top_bnodes([], _, _).
|
|
tw_top_bnodes([BNode-_|T], State, Out) :-
|
|
tw_bnode(BNode, State, Out),
|
|
tw_top_bnodes(T, State, Out).
|
|
|
|
|
|
tw_bnode(BNode, State, Out) :-
|
|
subject_triples(BNode, State, Pairs),
|
|
tw_bnode_triples(Pairs, State, Out),
|
|
format(Out, ' .~n', []).
|
|
|
|
tw_bnode_triples(Pairs, State, Out) :-
|
|
length(Pairs, Count),
|
|
inc_triple_count(State, Count),
|
|
group_po(Pairs, Grouped),
|
|
( tw_state_single_line_bnodes(State, true)
|
|
-> format(Out, '[ ', []),
|
|
tw_triples(Grouped, -1, State, Out),
|
|
format(Out, ' ]', [])
|
|
; line_position(Out, Indent),
|
|
format(Out, '[ ', []),
|
|
line_position(Out, AIndent),
|
|
tw_triples(Grouped, AIndent, State, Out),
|
|
nl_indent(Out, State, Indent),
|
|
format(Out, ']', [])
|
|
).
|
|
|
|
%% tw_cyclic_bnodes(+Pairs, +BNode, +State, +Out, +Cycle)
|
|
%
|
|
% The rest. These are groups of bnodes that are reachable, but we
|
|
% cannot find a starting point, neither from a named resource, nor
|
|
% from an unlinked bnode. As long as we are not considering stable
|
|
% canonical output, we can break the cycle at any point.
|
|
|
|
tw_cyclic_bnodes([], _State, _Out, _) :- !.
|
|
tw_cyclic_bnodes(Pairs, State, Out, Cycle0) :-
|
|
( tw_state_canonical(State, true)
|
|
-> sort_bnode_pairs(Pairs, BNodes, State)
|
|
; BNodes = Pairs
|
|
),
|
|
succ(Cycle0, Cycle),
|
|
BNodes = [BNode-Ref|_],
|
|
next_bnode_id(State, BNode, Ref),
|
|
comment(State, 'Breaking cycle ~D', [Cycle], Out),
|
|
tw_numbered_bnodes(Pairs, State, Out, 1, Rest),
|
|
tw_cyclic_bnodes(Rest, State, Out, Cycle).
|
|
|
|
|
|
%% tw_subject_triples(+Grouped, +Subject, +State, +Out)
|
|
%
|
|
% Save triples on Subject. Combine groups of triples with the
|
|
% same subject (;) and same subject+predicate (,).
|
|
%
|
|
% @param Subject is either a URI or an integer. The latter is
|
|
% used for writing a named bnode.
|
|
|
|
tw_subject_triples([], _, _, _) :- !.
|
|
tw_subject_triples(Grouped, URI, State, Out) :-
|
|
tw_state_group(State, false), !,
|
|
tw_ungrouped_triples(Grouped, URI, State, Out).
|
|
tw_subject_triples(Grouped, URI, State, Out) :-
|
|
tw_resource(URI, State, Out),
|
|
( tw_state_indent(State, Indent),
|
|
Indent > 0
|
|
-> nl_indent(Out, State, Indent)
|
|
; put_char(Out, ' '),
|
|
line_position(Out, Indent)
|
|
),
|
|
tw_triples(Grouped, Indent, State, Out),
|
|
format(Out, ' .~n', []).
|
|
|
|
%% tw_ungrouped_triples(+Grouped, +URI, +State, +Out)
|
|
%
|
|
% Write triples for subject URI as one line per triple. Used
|
|
% for canonical output.
|
|
|
|
tw_ungrouped_triples([], _, _, _).
|
|
tw_ungrouped_triples([P-Vs|Groups], URI, State, Out) :-
|
|
partition(rdf_is_bnode, Vs, BNVs, ProperVs),
|
|
tw_ungrouped_values(ProperVs, P, URI, State, Out),
|
|
sort_bnodes(BNVs, SortedBNVs, State),
|
|
tw_ungrouped_values(SortedBNVs, P, URI, State, Out),
|
|
tw_ungrouped_triples(Groups, URI, State, Out).
|
|
|
|
tw_ungrouped_values([], _, _, _, _).
|
|
tw_ungrouped_values([V|T], P, URI, State, Out) :-
|
|
tw_resource(URI, State, Out),
|
|
put_char(Out, ' '),
|
|
tw_predicate(P, State, Out),
|
|
put_char(Out, ' '),
|
|
tw_object(V, State, Out),
|
|
format(Out, ' .~n', []),
|
|
tw_ungrouped_values(T, P, URI, State, Out).
|
|
|
|
|
|
%% tw_triples(+Groups, +Indent, +State, +Out) is det.
|
|
%
|
|
% Triple writer that uses ; and ,- grouping
|
|
|
|
tw_triples([P-Vs|MoreGroups], Indent, State, Out) :-
|
|
tw_write_pvs(Vs, P, State, Out),
|
|
( MoreGroups == []
|
|
-> true
|
|
; format(Out, ' ;', []),
|
|
nl_indent(Out, State, Indent),
|
|
tw_triples(MoreGroups, Indent, State, Out)
|
|
).
|
|
|
|
tw_write_pvs(Values, P, State, Out) :-
|
|
tw_predicate(P, State, Out),
|
|
put_char(Out, ' '),
|
|
line_position(Out, Indent),
|
|
tw_write_vs(Values, Indent, State, Out).
|
|
|
|
tw_predicate(P, State, Out) :-
|
|
( rdf_equal(P, rdf:type)
|
|
-> format(Out, 'a', [])
|
|
; tw_resource(P, State, Out)
|
|
).
|
|
|
|
tw_write_vs([H|T], Indent, State, Out) :-
|
|
tw_object(H, State, Out),
|
|
( T == []
|
|
-> true
|
|
; format(Out, ' ,', []),
|
|
nl_indent(Out, State, Indent),
|
|
tw_write_vs(T, Indent, State, Out)
|
|
).
|
|
|
|
%% tw_object(+Value, +State, +Out) is det.
|
|
%
|
|
% Write the object of a triple.
|
|
|
|
tw_object(Value, State, Out) :-
|
|
rdf_is_bnode(Value), !,
|
|
tw_bnode_object(Value, State, Out).
|
|
tw_object(Value, State, Out) :-
|
|
atom(Value), !,
|
|
tw_resource(Value, State, Out).
|
|
tw_object(Literal, State, Out) :-
|
|
tw_literal(Literal, State, Out).
|
|
|
|
%% tw_bnode_object(+Value, +State, +Out) is det.
|
|
%
|
|
% Write a Bnode value. There are a number of cases:
|
|
%
|
|
% * The BNode was already written. Write the same ref.
|
|
% * The BNode is not shared. Inline and set =written=
|
|
% * The BNode is shared. Generate a NodeID and store it
|
|
% * The BNode is once as object: Generate a NodeID
|
|
% * The BNode is more than once object: Generate a NodeID
|
|
% and put in table.
|
|
|
|
tw_bnode_object(BNode, State, Out) :-
|
|
tw_state_nodeid_map(State, BNTree),
|
|
rb_lookup(BNode, Ref, BNTree), !,
|
|
( var(Ref)
|
|
-> ( tw_unshared_bnode(BNode, State, Out)
|
|
-> Ref = written
|
|
; next_bnode_id(State, BNode, Ref),
|
|
tw_bnode_ref(Ref, Out)
|
|
)
|
|
; tw_bnode_ref(Ref, Out)
|
|
).
|
|
tw_bnode_object(BNode, State, Out) :-
|
|
object_link_count(BNode, State, N),
|
|
N > 1, !,
|
|
tw_state_nodeid_map(State, BNTree0),
|
|
rb_insert(BNTree0, BNode, Ref, BNTree),
|
|
set_nodeid_map_of_tw_state(BNTree, State),
|
|
next_bnode_id(State, BNode, Ref),
|
|
tw_bnode_ref(Ref, Out).
|
|
tw_bnode_object(BNode, State, Out) :-
|
|
next_bnode_id(State, BNode, Ref),
|
|
tw_bnode_ref(Ref, Out).
|
|
|
|
tw_bnode_ref(bnode(Ref), Out) :-
|
|
( integer(Ref)
|
|
-> format(Out, '_:bn~w', [Ref])
|
|
; format(Out, '_:~w', [Ref])
|
|
).
|
|
|
|
%% tw_unshared_bnode(+BNode, +State, +Out) is semidet.
|
|
%
|
|
% Write a bnode if this is the only place it is used.
|
|
|
|
tw_unshared_bnode(BNode, State, Out) :-
|
|
object_link_count(BNode, State, 1),
|
|
subject_triples(BNode, State, Pairs),
|
|
( Pairs == []
|
|
-> format(Out, '[]', [])
|
|
; pairs_unshared_collection(Pairs, State, Collection)
|
|
-> ( Collection == []
|
|
-> format(Out, '()', [])
|
|
; tw_state_nodeid_map(State, BNTree),
|
|
rb_lookup(BNode, written, BNTree),
|
|
length(Collection, NMembers),
|
|
Triples is 2*NMembers,
|
|
inc_triple_count(State, Triples),
|
|
( tw_state_single_line_bnodes(State, true)
|
|
-> format(Out, '( ', []),
|
|
tw_collection(Collection, -1, State, Out),
|
|
format(Out, ' )', [])
|
|
; line_position(Out, Indent),
|
|
format(Out, '( ', []),
|
|
line_position(Out, AIndent),
|
|
tw_collection(Collection, AIndent, State, Out),
|
|
nl_indent(Out, State, Indent),
|
|
format(Out, ')', [])
|
|
)
|
|
)
|
|
; tw_bnode_triples(Pairs, State, Out)
|
|
).
|
|
|
|
tw_collection([H|T], Indent, State, Out) :-
|
|
tw_object(H, State, Out),
|
|
( T \== []
|
|
-> nl_indent(Out, State, Indent),
|
|
tw_collection(T, Indent, State, Out)
|
|
; true
|
|
).
|
|
|
|
%% unshared_collection(+URI, +State, -Members) is semidet.
|
|
%
|
|
% True if URI denodes an RDF list that is made up from bnodes, is
|
|
% linked exactly once to its context and contains no extra
|
|
% triples.
|
|
|
|
unshared_collection(C, _, []) :-
|
|
rdf_equal(C, rdf:nil), !.
|
|
unshared_collection(C, State, List) :-
|
|
rdf_is_bnode(C),
|
|
object_link_count(C, State, 1),
|
|
tw_state_nodeid_map(State, BNTree),
|
|
rb_lookup(C, written, BNTree),
|
|
subject_triples(C, State, Pairs),
|
|
pairs_unshared_collection(Pairs, State, List).
|
|
|
|
pairs_unshared_collection(Pairs, State, [H|T]) :-
|
|
rdf_equal(rdf:first, RDFFirst),
|
|
rdf_equal(rdf:rest, RDFRest),
|
|
Pairs = [ RDFFirst-H,
|
|
RDFRest-Rest
|
|
| More
|
|
],
|
|
( More == []
|
|
; rdf_equal(rdf:type, RDFType),
|
|
rdf_equal(rdf:'List', RDFList),
|
|
More == [RDFType-RDFList]
|
|
),
|
|
unshared_collection(Rest, State, T).
|
|
|
|
|
|
%% object_link_count(+BNode, +STate, -Count) is det.
|
|
%
|
|
% Number of times BNode is used as an object in the graph
|
|
|
|
object_link_count(BNode, State, Count) :-
|
|
tw_state_graph(State, Graph),
|
|
( var(Graph)
|
|
-> findall(S-P, rdf(S,P,BNode), Pairs0)
|
|
; findall(S-P, rdf(S,P,BNode,Graph), Pairs0)
|
|
),
|
|
sort(Pairs0, Pairs), % remove duplicates
|
|
length(Pairs, Count).
|
|
|
|
%% nl_indent(+Out, +State, +Indent) is det.
|
|
%
|
|
% Write a newline and indent to column Indent.
|
|
|
|
nl_indent(Out, _, -1) :- !,
|
|
put_char(Out, ' ').
|
|
nl_indent(Out, State, Indent) :-
|
|
nl(Out),
|
|
tw_state_tab_distance(State, TD),
|
|
( TD == 0
|
|
-> tab(Out, Indent)
|
|
; Tabs is Indent//TD,
|
|
Spaces is Indent mod TD,
|
|
put_n(Tabs, '\t', Out),
|
|
put_n(Spaces, ' ', Out)
|
|
).
|
|
|
|
put_n(N, Char, Out) :-
|
|
N > 0, !,
|
|
put_char(Out, Char),
|
|
N2 is N - 1,
|
|
put_n(N2, Char, Out).
|
|
put_n(_, _, _).
|
|
|
|
|
|
%% subject_triples(+URI, +State, -Pairs) is det.
|
|
%
|
|
% Pairs is a sorted list of P-O pairs representing all triples on
|
|
% the subject URI.
|
|
|
|
subject_triples(URI, State, Pairs) :-
|
|
tw_state_graph(State, Graph),
|
|
( var(Graph)
|
|
-> findall(P-O, rdf(URI, P, O), Pairs0)
|
|
; findall(P-O, rdf(URI, P, O, Graph), Pairs0)
|
|
),
|
|
sort(Pairs0, Pairs).
|
|
|
|
|
|
/*******************************
|
|
* GRAPH-LOGIC *
|
|
*******************************/
|
|
|
|
%% subjects(+State, -Subjects:ord_set) is det.
|
|
%
|
|
% Subjects is a list of all subjects in the graph requested in
|
|
% State.
|
|
|
|
subjects(State, Subjects) :-
|
|
findall(Subject, subject(State, Subject), AllSubjects),
|
|
sort(AllSubjects, Subjects).
|
|
|
|
subject(State, Subject) :-
|
|
tw_state_graph(State, Graph),
|
|
( atom(Graph)
|
|
-> rdf_subject(Subject),
|
|
( rdf(Subject, _, _, Graph)
|
|
-> true
|
|
)
|
|
; rdf_subject(Subject)
|
|
).
|
|
|
|
|
|
/*******************************
|
|
* CANONICAL ORDERING *
|
|
*******************************/
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
This section deals with the two problems of canonical graphs:
|
|
|
|
* Keep blank nodes in the same order
|
|
* Assign stable names to blank nodes that we need to
|
|
give a name. There are two cases: (1) a blank nodes is
|
|
used in more than one place and (2) a blank node series
|
|
is cyclic.
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
%% sort_bnodes(+BNodes, -Sorted, +State) is det.
|
|
%
|
|
% Sort a list of blank nodes.
|
|
|
|
sort_bnodes(BNodes, Sorted, _State) :-
|
|
sort(BNodes, Sorted).
|
|
|
|
%% sort_bnode_pairs(+Pairs, -Sorted, +State) is det.
|
|
%
|
|
% Sort a list of Pairs BNode-Ref
|
|
|
|
sort_bnode_pairs(Pairs, Sorted, _State) :-
|
|
sort(Pairs, Sorted).
|
|
|
|
%% bnode_to_term(+BNode, -Term, +State)
|
|
%
|
|
% Term is a canonical representation of the graph formed by BNode.
|
|
% The transformation of a bnode is
|
|
%
|
|
% bnode(p-[o1,o2,..], ..)
|
|
%
|
|
% The arguments are alphabetically sorted on predicate (can't we
|
|
% leave the preds out them?) and the objects are alphabetically
|
|
% sorted. Sorting multiple bnode values?
|
|
|
|
|
|
%% next_bnode_id(+State, +BNode, -Ref) is det.
|
|
%
|
|
% Generate a node-id for BNode. When writing non-canonically, we
|
|
% simply number the bnodes. Otherwise we want a more stable
|
|
% numbering. Our numbering is a hash of the content of the bnode.
|
|
% It is not unlikely that we find muliple copies, and therefore we
|
|
% number the full id is bn_<hash>_<n>, <n> counting 0...
|
|
|
|
next_bnode_id(State, _BNode, bnode(Ref)) :-
|
|
tw_state_canonical(State, false), !,
|
|
tw_state_bnode_id(State, Ref0),
|
|
Ref is Ref0+1,
|
|
nb_set_bnode_id_of_tw_state(Ref, State).
|
|
next_bnode_id(State, BNode, bnode(Ref)) :-
|
|
bnode_hash(BNode, Hash),
|
|
tw_state_bnode_hash(State, BNHash),
|
|
( var(BNHash)
|
|
-> rb_empty(BNHash)
|
|
; true
|
|
),
|
|
( rb_update(BNHash, Hash, C0, C, BNHash1)
|
|
-> C is C0+1
|
|
; C = 0,
|
|
rb_insert(BNHash, Hash, C, BNHash1)
|
|
),
|
|
set_bnode_hash_of_tw_state(BNHash1, State),
|
|
format(atom(Ref), 'bn_~w_~d', [Hash, C]).
|
|
|
|
%% bnode_hash(+BNode, -Hash) is det.
|
|
%
|
|
% Hash is the hash-value for a bnode.
|
|
%
|
|
% @tbd: Hash on content.
|
|
|
|
bnode_hash(BNode, Hash) :-
|
|
term_hash(BNode, Hash).
|
|
|
|
|
|
/*******************************
|
|
* PRIMITIVES *
|
|
*******************************/
|
|
|
|
%% tw_resource(+Resource, +State, +Out) is det.
|
|
%
|
|
% Write a resource
|
|
|
|
tw_resource(BNodeID, _, Out) :-
|
|
BNodeID = bnode(_), !,
|
|
tw_bnode_ref(BNodeID, Out).
|
|
tw_resource(Resource, State, Out) :-
|
|
tw_state_prefix_map(State, PrefixMap),
|
|
member(Prefix-Full, PrefixMap),
|
|
atom_concat(Full, Name, Resource),
|
|
turtle_name(Name), !,
|
|
format(Out, '~w:~w', [Prefix, Name]).
|
|
tw_resource(Resource, State, Out) :-
|
|
tw_relative_uri(Resource, State, Out).
|
|
|
|
|
|
tw_relative_uri(Resource, State, Out) :-
|
|
tw_state_base_root(State, Root),
|
|
atom(Root),
|
|
atom_concat(Root, ResPath, Resource),
|
|
sub_atom(ResPath, 0, _, _, /),
|
|
tw_state_base_path(State, BasePath),
|
|
relative_path(ResPath, BasePath, RelPath), !,
|
|
turtle_write_uri(Out, RelPath).
|
|
tw_relative_uri(Resource, _, Out) :-
|
|
turtle_write_uri(Out, Resource).
|
|
|
|
relative_path(Path, RelTo, RelPath) :-
|
|
atomic_list_concat(PL, /, Path),
|
|
atomic_list_concat(RL, /, RelTo),
|
|
delete_common_prefix(PL, RL, PL1, PL2),
|
|
to_dot_dot(PL2, DotDot, PL1),
|
|
atomic_list_concat(DotDot, /, RelPath).
|
|
|
|
delete_common_prefix([H|T01], [H|T02], T1, T2) :- !,
|
|
delete_common_prefix(T01, T02, T1, T2).
|
|
delete_common_prefix(T1, T2, T1, T2).
|
|
|
|
to_dot_dot([], Tail, Tail).
|
|
to_dot_dot([_], Tail, Tail) :- !.
|
|
to_dot_dot([_|T0], ['..'|T], Tail) :-
|
|
to_dot_dot(T0, T, Tail).
|
|
|
|
|
|
%% tw_literal(+Literal, +State, +Out) is det.
|
|
%
|
|
% Write a literal value to the stream Out.
|
|
|
|
tw_literal(literal(type(Type, Value)), State, Out) :- !,
|
|
tw_typed_literal(Type, Value, State, Out).
|
|
tw_literal(literal(lang(Lang, Value)), State, Out) :- !,
|
|
tw_quoted_string(Value, State, Out),
|
|
downcase_atom(Lang, TurtleLang), % Turtle lang = [a-z]+('-'[a-z0-9]+)*
|
|
format(Out, '@~w', [TurtleLang]).
|
|
tw_literal(literal(Value), State, Out) :-
|
|
atom(Value), !,
|
|
tw_quoted_string(Value, State, Out).
|
|
tw_literal(literal(Value), State, Out) :-
|
|
atom(Value), !,
|
|
tw_quoted_string(Value, State, Out).
|
|
% Add types automatically
|
|
tw_literal(literal(Value), State, Out) :-
|
|
integer(Value), !,
|
|
rdf_equal(Type, xsd:integer),
|
|
tw_typed_literal(Type, Value, State, Out).
|
|
tw_literal(literal(Value), State, Out) :-
|
|
float(Value), !,
|
|
rdf_equal(Type, xsd:double),
|
|
tw_typed_literal(Type, Value, State, Out).
|
|
tw_literal(literal(Value), State, Out) :-
|
|
xml_is_dom(Value), !,
|
|
rdf_equal(Type, rdf:'XMLLiteral'),
|
|
tw_typed_literal(Type, Value, State, Out).
|
|
tw_literal(Literal, _State, _Out) :-
|
|
type_error(rdf_literal, Literal).
|
|
|
|
|
|
tw_typed_literal(Type, Value, State, Out) :-
|
|
tw_abbreviated_literal(Type, Value, State, Out), !.
|
|
tw_typed_literal(Type, Value, State, Out) :-
|
|
(atom(Value) ; string(Value)), !,
|
|
tw_quoted_string(Value, State, Out),
|
|
write(Out, '^^'),
|
|
tw_resource(Type, State, Out).
|
|
tw_typed_literal(Type, Value, State, Out) :-
|
|
rdf_equal(Type, rdf:'XMLLiteral'), !,
|
|
with_output_to(string(Tmp),
|
|
xml_write(Value, [header(false)])),
|
|
tw_quoted_string(Tmp, State, Out),
|
|
write(Out, '^^'),
|
|
tw_resource(Type, State, Out).
|
|
tw_typed_literal(Type, Value, State, Out) :-
|
|
format(string(Tmp), '~q', [Value]),
|
|
tw_quoted_string(Tmp, State, Out),
|
|
write(Out, '^^'),
|
|
tw_resource(Type, State, Out).
|
|
|
|
|
|
%% tw_abbreviated_literal(+Type, +Value, +State, +Out) is semidet.
|
|
%
|
|
% Turtle abbreviated typed literals.
|
|
%
|
|
% @tbd: Deal with canonical forms (or is this a task of the
|
|
% RDF parser?
|
|
% @tbd: What if the value is not in the lexical space of the type?
|
|
|
|
term_expansion((tw_abbreviated_literal(NS:Local, Value, State, Out) :- Body),
|
|
(tw_abbreviated_literal(Type, Value, State, Out) :- Body)) :-
|
|
atom(NS),
|
|
rdf_global_id(NS:Local, Type).
|
|
|
|
tw_abbreviated_literal(xsd:integer, Value, State, Out) :-
|
|
( tw_state_canonize_numbers(State, false)
|
|
-> write(Out, Value)
|
|
; atom_number(Value, Int),
|
|
format(Out, '~d', [Int])
|
|
).
|
|
tw_abbreviated_literal(xsd:double, Value, State, Out) :-
|
|
( tw_state_canonize_numbers(State, false)
|
|
-> write(Out, Value)
|
|
; atom_number(Value, Float),
|
|
format(Out, '~f', [Float])
|
|
).
|
|
tw_abbreviated_literal(xsd:decimal, Value, _, Out) :-
|
|
format(Out, '~w', [Value]).
|
|
tw_abbreviated_literal(xsd:boolean, Value, _, Out) :-
|
|
format(Out, '~w', [Value]).
|
|
|
|
|
|
%% tw_quoted_string(+Atom, +State, +Out) is det.
|
|
%
|
|
% Write Atom to Out as a quoted string. We only use the
|
|
% single-"..." representation.
|
|
|
|
tw_quoted_string(Atom, _, Out) :-
|
|
turtle_write_quoted_string(Out, Atom).
|
|
|
|
|
|
/*******************************
|
|
* COMMENT *
|
|
*******************************/
|
|
|
|
comment(State, Format, Args, Out) :-
|
|
tw_state_comment(State, true), !,
|
|
format(Out, '~n# ', []),
|
|
format(Out, Format, Args),
|
|
format(Out, '~n', []).
|
|
comment(_, _, _, _).
|
|
|
|
|
|
|
|
/*******************************
|
|
* STATISTICS *
|
|
*******************************/
|
|
|
|
inc_triple_count(State, Count) :-
|
|
tw_state_triple_count(State, C0),
|
|
C1 is C0+Count,
|
|
nb_set_triple_count_of_tw_state(C1, State).
|
|
|
|
:- multifile
|
|
prolog:message//1.
|
|
|
|
prolog:message(rdf(saved(File, Time, SavedSubjects, SavedTriples))) -->
|
|
[ 'Saved ~D triples about ~D subjects into ~p (~3f sec)'-
|
|
[SavedTriples, SavedSubjects, File, Time]
|
|
].
|