636 lines
19 KiB
Perl
636 lines
19 KiB
Perl
|
/* $Id$
|
||
|
|
||
|
Part of SWI-Prolog
|
||
|
|
||
|
Author: Jan Wielemaker
|
||
|
E-mail: J.Wielemak@uva.nl
|
||
|
WWW: http://www.swi-prolog.org
|
||
|
Copyright (C): 2004-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 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_write,
|
||
|
[ rdf_write_xml/2 % +Stream, +Triples
|
||
|
]).
|
||
|
:- use_module(library('semweb/rdf_db')).
|
||
|
:- use_module(library(lists)).
|
||
|
:- use_module(library(sgml)).
|
||
|
:- use_module(library(sgml_write)).
|
||
|
:- use_module(library(assoc)).
|
||
|
:- use_module(library(pairs)).
|
||
|
:- use_module(library(debug)).
|
||
|
|
||
|
|
||
|
/** <module> Write RDF/XML from a list of triples
|
||
|
|
||
|
This module writes an RDF/XML document from a list of triples of the
|
||
|
format rdf(Subject, Predicate, Object). It is primarily intended for
|
||
|
communicating computed RDF model fragments to external programs using
|
||
|
RDF/XML.
|
||
|
|
||
|
When used from the HTTP library, use the following code:
|
||
|
|
||
|
==
|
||
|
reply_graph(RDF) :-
|
||
|
format('Content-type: application/rdf+xml; charset=UTF-8~n~n'),
|
||
|
rdf_write_xml(current_output, RDF).
|
||
|
==
|
||
|
|
||
|
@author Jan Wielemaker
|
||
|
@see library(semweb/rdf_db) offers saving a named graph directly from
|
||
|
the RDF database.
|
||
|
*/
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* WRITE RDFXML *
|
||
|
*******************************/
|
||
|
|
||
|
%% rdf_write_xml(+Out:stream, +Triples:list(rdf(S,P,O))) is det.
|
||
|
%
|
||
|
% Write an RDF/XML serialization of Triples to Out.
|
||
|
|
||
|
rdf_write_xml(Out, Triples) :-
|
||
|
sort(Triples, Unique),
|
||
|
rdf_write_header(Out, Unique),
|
||
|
node_id_map(Unique, AnonIDs),
|
||
|
rdf_write_triples(Unique, AnonIDs, Out),
|
||
|
rdf_write_footer(Out).
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* HEADER/FOOTER *
|
||
|
*******************************/
|
||
|
|
||
|
%% rdf_write_header(+Out, +Triples)
|
||
|
%
|
||
|
% Save XML document header, doctype and open the RDF environment.
|
||
|
% This predicate also sets up the namespace notation.
|
||
|
|
||
|
rdf_write_header(Out, Triples) :-
|
||
|
xml_encoding(Out, Enc, Encoding),
|
||
|
format(Out, '<?xml version=\'1.0\' encoding=\'~w\'?>~n', [Encoding]),
|
||
|
format(Out, '<!DOCTYPE rdf:RDF [', []),
|
||
|
used_namespaces(Triples, NSList),
|
||
|
( member(Id, NSList),
|
||
|
ns(Id, NS),
|
||
|
rdf_quote_uri(NS, QNS),
|
||
|
xml_quote_attribute(QNS, NSText0, Enc),
|
||
|
xml_escape_parameter_entity(NSText0, NSText),
|
||
|
format(Out, '~N <!ENTITY ~w \'~w\'>', [Id, NSText]),
|
||
|
fail
|
||
|
; true
|
||
|
),
|
||
|
format(Out, '~N]>~n~n', []),
|
||
|
format(Out, '<rdf:RDF', []),
|
||
|
( member(Id, NSList),
|
||
|
format(Out, '~N xmlns:~w="&~w;"~n', [Id, Id]),
|
||
|
fail
|
||
|
; true
|
||
|
),
|
||
|
format(Out, '>~n', []).
|
||
|
|
||
|
|
||
|
xml_encoding(Out, Enc, Encoding) :-
|
||
|
stream_property(Out, encoding(Enc)),
|
||
|
( xml_encoding_name(Enc, Encoding)
|
||
|
-> true
|
||
|
; throw(error(domain_error(rdf_encoding, Enc), _))
|
||
|
).
|
||
|
|
||
|
xml_encoding_name(ascii, 'US-ASCII').
|
||
|
xml_encoding_name(iso_latin_1, 'ISO-8859-1').
|
||
|
xml_encoding_name(utf8, 'UTF-8').
|
||
|
|
||
|
%% xml_escape_parameter_entity(+In, -Out) is det.
|
||
|
%
|
||
|
% Escape % as % for entity declarations.
|
||
|
|
||
|
xml_escape_parameter_entity(In, Out) :-
|
||
|
sub_atom(In, _, _, _, '%'), !,
|
||
|
atom_codes(In, Codes),
|
||
|
phrase(escape_parent(Codes), OutCodes),
|
||
|
atom_codes(Out, OutCodes).
|
||
|
xml_escape_parameter_entity(In, In).
|
||
|
|
||
|
escape_parent([]) --> [].
|
||
|
escape_parent([H|T]) -->
|
||
|
( { H == 37 }
|
||
|
-> "%"
|
||
|
; [H]
|
||
|
),
|
||
|
escape_parent(T).
|
||
|
|
||
|
%% used_namespaces(+Triples:list(rdf(S,P,O)), -List:atom) is det.
|
||
|
%
|
||
|
% Return the list of namespace abbreviations used in a set of
|
||
|
% triples.
|
||
|
|
||
|
used_namespaces(Triples, NSList) :-
|
||
|
decl_used_predicate_ns(Triples),
|
||
|
resources(Triples, Resources),
|
||
|
empty_assoc(A0),
|
||
|
put_assoc(rdf, A0, *, A1), % needed for rdf:RDF
|
||
|
res_used_namespaces(Resources, _NoNS, A1, A),
|
||
|
assoc_to_keys(A, NSList).
|
||
|
|
||
|
|
||
|
res_used_namespaces([], [], A, A).
|
||
|
res_used_namespaces([Resource|T], NoNS, A0, A) :-
|
||
|
ns(NS, Full),
|
||
|
Full \== '',
|
||
|
atom_concat(Full, _Local, Resource), !,
|
||
|
put_assoc(NS, A0, *, A1),
|
||
|
res_used_namespaces(T, NoNS, A1, A).
|
||
|
res_used_namespaces([R|T0], [R|T], A0, A) :-
|
||
|
res_used_namespaces(T0, T, A0, A).
|
||
|
|
||
|
%% resources(+Triples:list(rdf(S,P,O)), -Resources:list(atom)) is det.
|
||
|
%
|
||
|
% Resources is the set of resources referenced in Triples.
|
||
|
|
||
|
resources(Triples, Resources) :-
|
||
|
phrase(resources(Triples), Raw),
|
||
|
sort(Raw, Resources).
|
||
|
|
||
|
resources([]) -->
|
||
|
[].
|
||
|
resources([rdf(S,P,O)|T]) -->
|
||
|
[S,P],
|
||
|
object_resources(O),
|
||
|
resources(T).
|
||
|
|
||
|
object_resources(Atom) -->
|
||
|
{ atom(Atom) }, !,
|
||
|
[ Atom ].
|
||
|
object_resources(literal(type(Type, _))) --> !,
|
||
|
[ Type ].
|
||
|
object_resources(_) -->
|
||
|
[].
|
||
|
|
||
|
%% decl_used_predicate_ns(+Triples:list(rdf(S,P,O)))
|
||
|
%
|
||
|
% For every URL used as a predicate we *MUST* define a namespace
|
||
|
% as we cannot use names holding /, :, etc. as XML identifiers.
|
||
|
|
||
|
:- thread_local
|
||
|
predicate_ns/2.
|
||
|
|
||
|
decl_used_predicate_ns(Triples) :-
|
||
|
retractall(predicate_ns(_,_)),
|
||
|
( member(rdf(_,P,_), Triples),
|
||
|
decl_predicate_ns(P),
|
||
|
fail
|
||
|
; true
|
||
|
).
|
||
|
|
||
|
decl_predicate_ns(Pred) :-
|
||
|
predicate_ns(Pred, _), !.
|
||
|
decl_predicate_ns(Pred) :-
|
||
|
rdf_global_id(NS:_Local, Pred),
|
||
|
assert(predicate_ns(Pred, NS)), !.
|
||
|
decl_predicate_ns(Pred) :-
|
||
|
is_bag_li_predicate(Pred), !.
|
||
|
decl_predicate_ns(Pred) :-
|
||
|
atom_codes(Pred, Codes),
|
||
|
append(NSCodes, LocalCodes, Codes),
|
||
|
xml_codes(LocalCodes), !,
|
||
|
( NSCodes \== []
|
||
|
-> atom_codes(NS, NSCodes),
|
||
|
( ns(Id, NS)
|
||
|
-> assert(predicate_ns(Pred, Id))
|
||
|
; between(1, infinite, N),
|
||
|
atom_concat(ns, N, Id),
|
||
|
\+ ns(Id, _)
|
||
|
-> rdf_register_ns(Id, NS),
|
||
|
print_message(informational,
|
||
|
rdf(using_namespace(Id, NS)))
|
||
|
),
|
||
|
assert(predicate_ns(Pred, Id))
|
||
|
; assert(predicate_ns(Pred, -)) % no namespace used
|
||
|
).
|
||
|
|
||
|
xml_codes([]).
|
||
|
xml_codes([H|T]) :-
|
||
|
xml_code(H),
|
||
|
xml_codes(T).
|
||
|
|
||
|
xml_code(X) :-
|
||
|
code_type(X, csym), !.
|
||
|
xml_code(0'-). % '
|
||
|
|
||
|
|
||
|
rdf_write_footer(Out) :-
|
||
|
format(Out, '</rdf:RDF>~n', []).
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* ANONYMOUS IDS *
|
||
|
*******************************/
|
||
|
|
||
|
%% node_id_map(+Triples, -IdMap) is det.
|
||
|
%
|
||
|
% Create an assoc Resource -> NodeID for those anonymous resources
|
||
|
% in Triples that need a NodeID. This implies all anonymous
|
||
|
% resources that are used multiple times as object value.
|
||
|
|
||
|
node_id_map(Triples, IdMap) :-
|
||
|
anonymous_objects(Triples, Objs),
|
||
|
msort(Objs, Sorted),
|
||
|
empty_assoc(IdMap0),
|
||
|
nodeid_map(Sorted, 0, IdMap0, IdMap).
|
||
|
|
||
|
anonymous_objects([], []).
|
||
|
anonymous_objects([rdf(_,_,O)|T0], Anon) :-
|
||
|
rdf_is_bnode(O), !,
|
||
|
Anon = [O|T],
|
||
|
anonymous_objects(T0, T).
|
||
|
anonymous_objects([_|T0], T) :-
|
||
|
anonymous_objects(T0, T).
|
||
|
|
||
|
nodeid_map([], _, Map, Map).
|
||
|
nodeid_map([H,H|T0], Id, Map0, Map) :- !,
|
||
|
remove_leading(H, T0, T),
|
||
|
atom_concat(bn, Id, NodeId),
|
||
|
put_assoc(H, Map0, NodeId, Map1),
|
||
|
Id2 is Id + 1,
|
||
|
nodeid_map(T, Id2, Map1, Map).
|
||
|
nodeid_map([_|T], Id, Map0, Map) :-
|
||
|
nodeid_map(T, Id, Map0, Map).
|
||
|
|
||
|
remove_leading(H, [H|T0], T) :- !,
|
||
|
remove_leading(H, T0, T).
|
||
|
remove_leading(_, T, T).
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* TRIPLES *
|
||
|
*******************************/
|
||
|
|
||
|
rdf_write_triples(Triples, NodeIDs, Out) :-
|
||
|
rdf_write_triples(Triples, NodeIDs, Out, [], Anon),
|
||
|
rdf_write_anon(Anon, NodeIDs, Out, Anon).
|
||
|
|
||
|
rdf_write_triples([], _, _, Anon, Anon).
|
||
|
rdf_write_triples([H|T0], NodeIDs, Out, Anon0, Anon) :-
|
||
|
arg(1, H, S),
|
||
|
subject_triples(S, [H|T0], T, OnSubject),
|
||
|
( rdf_is_bnode(S)
|
||
|
-> rdf_write_triples(T, NodeIDs, Out, [anon(S,_,OnSubject)|Anon0], Anon)
|
||
|
; rdf_write_subject(OnSubject, S, NodeIDs, Out, Anon0),
|
||
|
rdf_write_triples(T, NodeIDs, Out, Anon0, Anon)
|
||
|
).
|
||
|
|
||
|
subject_triples(S, [H|T0], T, [H|M]) :-
|
||
|
arg(1, H, S), !,
|
||
|
subject_triples(S, T0, T, M).
|
||
|
subject_triples(_, T, T, []).
|
||
|
|
||
|
|
||
|
rdf_write_anon([], _, _, _).
|
||
|
rdf_write_anon([anon(Subject, Done, Triples)|T], NodeIDs, Out, Anon) :-
|
||
|
Done \== true, !,
|
||
|
Done = true,
|
||
|
rdf_write_subject(Triples, Subject, NodeIDs, Out, Anon),
|
||
|
rdf_write_anon(T, NodeIDs, Out, Anon).
|
||
|
rdf_write_anon([_|T], NodeIDs, Out, Anon) :-
|
||
|
rdf_write_anon(T, NodeIDs, Out, Anon).
|
||
|
|
||
|
rdf_write_subject(Triples, Subject, NodeIDs, Out, Anon) :-
|
||
|
rdf_write_subject(Triples, Out, Subject, NodeIDs, -, 0, Anon), !,
|
||
|
format(Out, '~n', []).
|
||
|
rdf_write_subject(_, Subject, _, _, _) :-
|
||
|
throw(error(rdf_save_failed(Subject), 'Internal error')).
|
||
|
|
||
|
rdf_write_subject(Triples, Out, Subject, NodeIDs, DefNS, Indent, Anon) :-
|
||
|
rdf_equal(rdf:type, RdfType),
|
||
|
select(rdf(_, RdfType,Type), Triples, Triples1),
|
||
|
rdf_id(Type, DefNS, TypeId),
|
||
|
xml_is_name(TypeId), !,
|
||
|
format(Out, '~*|<', [Indent]),
|
||
|
rdf_write_id(Out, TypeId),
|
||
|
save_about(Out, Subject, NodeIDs),
|
||
|
save_attributes(Triples1, DefNS, Out, NodeIDs, TypeId, Indent, Anon).
|
||
|
rdf_write_subject(Triples, Out, Subject, NodeIDs, _DefNS, Indent, Anon) :-
|
||
|
format(Out, '~*|<rdf:Description', [Indent]),
|
||
|
save_about(Out, Subject, NodeIDs),
|
||
|
save_attributes(Triples, rdf, Out, NodeIDs, rdf:'Description', Indent, Anon).
|
||
|
|
||
|
xml_is_name(_NS:Atom) :- !,
|
||
|
xml_name(Atom).
|
||
|
xml_is_name(Atom) :-
|
||
|
xml_name(Atom).
|
||
|
|
||
|
save_about(Out, Subject, NodeIDs) :-
|
||
|
rdf_is_bnode(Subject), !,
|
||
|
( get_assoc(Subject, NodeIDs, NodeID)
|
||
|
-> format(Out,' rdf:nodeID="~w"', [NodeID])
|
||
|
; true
|
||
|
).
|
||
|
save_about(Out, Subject, _) :-
|
||
|
stream_property(Out, encoding(Encoding)),
|
||
|
rdf_value(Subject, QSubject, Encoding),
|
||
|
format(Out, ' rdf:about="~w"', [QSubject]), !.
|
||
|
save_about(_, _, _) :-
|
||
|
assertion(fail).
|
||
|
|
||
|
%% save_attributes(+List, +DefNS, +Out, +NodeIDs, Element, +Indent, +Anon)
|
||
|
%
|
||
|
% Save the attributes. Short literal attributes are saved in the
|
||
|
% tag. Others as the content of the description element. The
|
||
|
% begin tag has already been filled.
|
||
|
|
||
|
save_attributes(Triples, DefNS, Out, NodeIDs, Element, Indent, Anon) :-
|
||
|
split_attributes(Triples, InTag, InBody),
|
||
|
SubIndent is Indent + 2,
|
||
|
save_attributes2(InTag, DefNS, tag, Out, NodeIDs, SubIndent, Anon),
|
||
|
( InBody == []
|
||
|
-> format(Out, '/>~n', [])
|
||
|
; format(Out, '>~n', []),
|
||
|
save_attributes2(InBody, _, body, Out, NodeIDs, SubIndent, Anon),
|
||
|
format(Out, '~N~*|</~w>~n', [Indent, Element])
|
||
|
).
|
||
|
|
||
|
% split_attributes(+Triples, -HeadAttrs, -BodyAttr)
|
||
|
%
|
||
|
% Split attribute (Name=Value) list into attributes for the head
|
||
|
% and body. Attributes can only be in the head if they are literal
|
||
|
% and appear only one time in the attribute list.
|
||
|
|
||
|
split_attributes(Triples, HeadAttr, BodyAttr) :-
|
||
|
duplicate_attributes(Triples, Dupls, Singles),
|
||
|
simple_literal_attributes(Singles, HeadAttr, Rest),
|
||
|
append(Dupls, Rest, BodyAttr).
|
||
|
|
||
|
% duplicate_attributes(+Attrs, -Duplicates, -Singles)
|
||
|
%
|
||
|
% Extract attributes that appear more than onces as we cannot
|
||
|
% dublicate an attribute in the head according to the XML rules.
|
||
|
|
||
|
duplicate_attributes([], [], []).
|
||
|
duplicate_attributes([H|T], Dupls, Singles) :-
|
||
|
arg(2, H, Name),
|
||
|
named_attributes(Name, T, D, R),
|
||
|
D \== [],
|
||
|
append([H|D], Dupls2, Dupls), !,
|
||
|
duplicate_attributes(R, Dupls2, Singles).
|
||
|
duplicate_attributes([H|T], Dupls2, [H|Singles]) :-
|
||
|
duplicate_attributes(T, Dupls2, Singles).
|
||
|
|
||
|
named_attributes(_, [], [], []) :- !.
|
||
|
named_attributes(Name, [H|T], D, R) :-
|
||
|
( arg(2, H, Name)
|
||
|
-> D = [H|DT],
|
||
|
named_attributes(Name, T, DT, R)
|
||
|
; R = [H|RT],
|
||
|
named_attributes(Name, T, D, RT)
|
||
|
).
|
||
|
|
||
|
% simple_literal_attributes(+Attributes, -Inline, -Body)
|
||
|
%
|
||
|
% Split attributes for (literal) attributes to be used in the
|
||
|
% begin-tag and ones that have to go into the body of the description.
|
||
|
|
||
|
simple_literal_attributes([], [], []).
|
||
|
simple_literal_attributes([H|TA], [H|TI], B) :-
|
||
|
in_tag_attribute(H), !,
|
||
|
simple_literal_attributes(TA, TI, B).
|
||
|
simple_literal_attributes([H|TA], I, [H|TB]) :-
|
||
|
simple_literal_attributes(TA, I, TB).
|
||
|
|
||
|
in_tag_attribute(rdf(_,P,literal(Text))) :-
|
||
|
atom(Text), % may not have lang qualifier
|
||
|
atom_length(Text, Len),
|
||
|
Len < 60,
|
||
|
\+ is_bag_li_predicate(P).
|
||
|
|
||
|
|
||
|
% save_attributes(+List, +DefNS, +TagOrBody, +Out, +NodeIDs, +Indent, +Anon)
|
||
|
%
|
||
|
% Save a list of attributes.
|
||
|
|
||
|
save_attributes2([], _, _, _, _, _, _).
|
||
|
save_attributes2([H|T], DefNS, Where, Out, NodeIDs, Indent, Anon) :-
|
||
|
save_attribute(Where, H, DefNS, Out, NodeIDs, Indent, Anon),
|
||
|
save_attributes2(T, DefNS, Where, Out, NodeIDs, Indent, Anon).
|
||
|
|
||
|
%% save_attribute(+Where, +Triple, +DefNS, +Out, +NodeIDs, +Indent, +Anon)
|
||
|
|
||
|
save_attribute(tag, rdf(_, Name, literal(Value)), DefNS, Out, _, Indent, _Anon) :-
|
||
|
AttIndent is Indent + 2,
|
||
|
rdf_att_id(Name, DefNS, NameText),
|
||
|
stream_property(Out, encoding(Encoding)),
|
||
|
xml_quote_attribute(Value, QVal, Encoding),
|
||
|
format(Out, '~N~*|', [AttIndent]),
|
||
|
rdf_write_id(Out, NameText),
|
||
|
format(Out, '="~w"', [QVal]).
|
||
|
save_attribute(body, rdf(_,Name,literal(Literal)), DefNS, Out, _, Indent, _) :- !,
|
||
|
rdf_p_id(Name, DefNS, NameText),
|
||
|
format(Out, '~N~*|<', [Indent]),
|
||
|
rdf_write_id(Out, NameText),
|
||
|
( Literal = lang(Lang, Value)
|
||
|
-> rdf_id(Lang, DefNS, LangText),
|
||
|
format(Out, ' xml:lang="~w">', [LangText])
|
||
|
; Literal = type(Type, Value)
|
||
|
-> ( rdf_equal(Type, rdf:'XMLLiteral')
|
||
|
-> write(Out, ' rdf:parseType="Literal">'),
|
||
|
Value = Literal
|
||
|
; stream_property(Out, encoding(Encoding)),
|
||
|
rdf_value(Type, QVal, Encoding),
|
||
|
format(Out, ' rdf:datatype="~w">', [QVal])
|
||
|
)
|
||
|
; atomic(Literal)
|
||
|
-> write(Out, '>'),
|
||
|
Value = Literal
|
||
|
; write(Out, ' rdf:parseType="Literal">'),
|
||
|
Value = Literal
|
||
|
),
|
||
|
save_attribute_value(Value, Out, Indent),
|
||
|
write(Out, '</'), rdf_write_id(Out, NameText), write(Out, '>').
|
||
|
save_attribute(body, rdf(_, Name, Value), DefNS, Out, NodeIDs, Indent, Anon) :-
|
||
|
rdf_is_bnode(Value),
|
||
|
memberchk(anon(Value, Done, ValueTriples), Anon), !,
|
||
|
rdf_p_id(Name, DefNS, NameText),
|
||
|
format(Out, '~N~*|<', [Indent]),
|
||
|
rdf_write_id(Out, NameText),
|
||
|
( var(Done)
|
||
|
-> Done = true,
|
||
|
SubIndent is Indent + 2,
|
||
|
( rdf_equal(RdfType, rdf:type),
|
||
|
rdf_equal(ListClass, rdf:'List'),
|
||
|
memberchk(rdf(_, RdfType, ListClass), ValueTriples)
|
||
|
-> format(Out, ' rdf:parseType="Collection">~n', []),
|
||
|
rdf_save_list(ValueTriples, Out, Value, NodeIDs, DefNS, SubIndent, Anon)
|
||
|
; format(Out, '>~n', []),
|
||
|
rdf_write_subject(ValueTriples, Out, Value, NodeIDs, DefNS, SubIndent, Anon)
|
||
|
),
|
||
|
format(Out, '~N~*|</', [Indent]),
|
||
|
rdf_write_id(Out, NameText),
|
||
|
format(Out, '>~n', [])
|
||
|
; get_assoc(Value, NodeIDs, NodeID)
|
||
|
-> format(Out, ' rdf:nodeID="~w"/>', [NodeID])
|
||
|
; assertion(fail)
|
||
|
).
|
||
|
save_attribute(body, rdf(_, Name, Value), DefNS, Out, _, Indent, _Anon) :-
|
||
|
stream_property(Out, encoding(Encoding)),
|
||
|
rdf_value(Value, QVal, Encoding),
|
||
|
rdf_p_id(Name, DefNS, NameText),
|
||
|
format(Out, '~N~*|<', [Indent]),
|
||
|
rdf_write_id(Out, NameText),
|
||
|
format(Out, ' rdf:resource="~w"/>', [QVal]).
|
||
|
|
||
|
save_attribute_value(Value, Out, _) :- % strings
|
||
|
atom(Value), !,
|
||
|
stream_property(Out, encoding(Encoding)),
|
||
|
xml_quote_cdata(Value, QVal, Encoding),
|
||
|
write(Out, QVal).
|
||
|
save_attribute_value(Value, Out, _) :- % numbers
|
||
|
number(Value), !,
|
||
|
writeq(Out, Value). % quoted: preserve floats
|
||
|
save_attribute_value(Value, Out, Indent) :-
|
||
|
xml_is_dom(Value), !,
|
||
|
XMLIndent is Indent+2,
|
||
|
xml_write(Out, Value,
|
||
|
[ header(false),
|
||
|
indent(XMLIndent)
|
||
|
]).
|
||
|
save_attribute_value(Value, _Out, _) :-
|
||
|
throw(error(save_attribute_value(Value), _)).
|
||
|
|
||
|
rdf_save_list(_, _, List, _, _, _, _) :-
|
||
|
rdf_equal(List, rdf:nil), !.
|
||
|
rdf_save_list(ListTriples, Out, List, NodeIDs, DefNS, Indent, Anon) :-
|
||
|
rdf_equal(RdfFirst, rdf:first),
|
||
|
memberchk(rdf(List, RdfFirst, First), ListTriples),
|
||
|
( rdf_is_bnode(First),
|
||
|
memberchk(anon(First, true, FirstTriples), Anon)
|
||
|
-> nl(Out),
|
||
|
rdf_write_subject(FirstTriples, Out, First, NodeIDs, DefNS, Indent, Anon)
|
||
|
; stream_property(Out, encoding(Encoding)),
|
||
|
rdf_value(First, QVal, Encoding),
|
||
|
format(Out, '~N~*|<rdf:Description about="~w"/>',
|
||
|
[Indent, QVal])
|
||
|
),
|
||
|
( rdf_equal(RdfRest, rdf:rest),
|
||
|
memberchk(rdf(List, RdfRest, List2), ListTriples),
|
||
|
\+ rdf_equal(List2, rdf:nil),
|
||
|
memberchk(anon(List2, true, List2Triples), Anon)
|
||
|
-> rdf_save_list(List2Triples, Out, List2, NodeIDs, DefNS, Indent, Anon)
|
||
|
; true
|
||
|
).
|
||
|
|
||
|
%% rdf_p_id(+Resource, +DefNS, -NSLocal)
|
||
|
%
|
||
|
% As rdf_id/3 for predicate names. Maps _:<N> to rdf:li.
|
||
|
%
|
||
|
% @tbd Ensure we are talking about an rdf:Bag
|
||
|
|
||
|
rdf_p_id(LI, _, 'rdf:li') :-
|
||
|
is_bag_li_predicate(LI), !.
|
||
|
rdf_p_id(Resource, DefNS, NSLocal) :-
|
||
|
rdf_id(Resource, DefNS, NSLocal).
|
||
|
|
||
|
%% is_bag_li_predicate(+Pred) is semidet.
|
||
|
%
|
||
|
% True if Pred is _:N, as used for members of an rdf:Bag, rdf:Seq
|
||
|
% or rdf:Alt.
|
||
|
|
||
|
is_bag_li_predicate(Pred) :-
|
||
|
atom_concat('_:', AN, Pred),
|
||
|
catch(atom_number(AN, N), _, true), integer(N), N >= 0, !.
|
||
|
|
||
|
|
||
|
%% rdf_id(+Resource, +DefNS, -NSLocal)
|
||
|
%
|
||
|
% Generate a NS:Local name for Resource given the indicated
|
||
|
% default namespace. This call is used for elements.
|
||
|
|
||
|
rdf_id(Id, NS, NS:Local) :-
|
||
|
ns(NS, Full),
|
||
|
Full \== '',
|
||
|
atom_concat(Full, Local, Id), !.
|
||
|
rdf_id(Id, _, NS:Local) :-
|
||
|
ns(NS, Full),
|
||
|
Full \== '',
|
||
|
atom_concat(Full, Local, Id), !.
|
||
|
rdf_id(Id, _, Id).
|
||
|
|
||
|
|
||
|
%% rdf_write_id(+Out, +NSLocal) is det.
|
||
|
%
|
||
|
% Write an identifier. We cannot use native write on it as both NS
|
||
|
% and Local can be operators.
|
||
|
|
||
|
rdf_write_id(Out, NS:Local) :- !,
|
||
|
format(Out, '~w:~w', [NS, Local]).
|
||
|
rdf_write_id(Out, Atom) :-
|
||
|
write(Out, Atom).
|
||
|
|
||
|
|
||
|
rdf_att_id(Id, _, NS:Local) :-
|
||
|
ns(NS, Full),
|
||
|
Full \== '',
|
||
|
atom_concat(Full, Local, Id), !.
|
||
|
rdf_att_id(Id, _, Id).
|
||
|
|
||
|
|
||
|
%% rdf_value(+Resource, -Text, +Encoding)
|
||
|
%
|
||
|
% According to "6.4 RDF URI References" of the RDF Syntax
|
||
|
% specification, a URI reference is UNICODE string not containing
|
||
|
% control sequences, represented as UTF-8 and then as escaped
|
||
|
% US-ASCII.
|
||
|
%
|
||
|
% NOTE: the to_be_described/1 trick ensures entity rewrite in
|
||
|
% resources that start with 'http://t-d-b.org?'. This is a of a
|
||
|
% hack to save the artchive data in the MultimediaN project. We
|
||
|
% should use a more general mechanism.
|
||
|
|
||
|
rdf_value(V, Text, Encoding) :-
|
||
|
to_be_described(Prefix),
|
||
|
atom_concat(Prefix, V1, V),
|
||
|
ns(NS, Full),
|
||
|
atom_concat(Full, Local, V1), !,
|
||
|
rdf_quote_uri(Local, QLocal0),
|
||
|
xml_quote_attribute(QLocal0, QLocal, Encoding),
|
||
|
concat_atom([Prefix, '&', NS, (';'), QLocal], Text).
|
||
|
rdf_value(V, Text, Encoding) :-
|
||
|
ns(NS, Full),
|
||
|
atom_concat(Full, Local, V), !,
|
||
|
rdf_quote_uri(Local, QLocal0),
|
||
|
xml_quote_attribute(QLocal0, QLocal, Encoding),
|
||
|
concat_atom(['&', NS, (';'), QLocal], Text).
|
||
|
rdf_value(V, Q, Encoding) :-
|
||
|
rdf_quote_uri(V, Q0),
|
||
|
xml_quote_attribute(Q0, Q, Encoding).
|
||
|
|
||
|
to_be_described('http://t-d-b.org?').
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* UTIL *
|
||
|
*******************************/
|
||
|
|
||
|
ns(Id, Full) :-
|
||
|
rdf_db:ns(Id, Full).
|