View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2010-2013, University of Amsterdam
    7    All rights reserved.
    8
    9    Redistribution and use in source and binary forms, with or without
   10    modification, are permitted provided that the following conditions
   11    are met:
   12
   13    1. Redistributions of source code must retain the above copyright
   14       notice, this list of conditions and the following disclaimer.
   15
   16    2. Redistributions in binary form must reproduce the above copyright
   17       notice, this list of conditions and the following disclaimer in
   18       the documentation and/or other materials provided with the
   19       distribution.
   20
   21    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   22    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   23    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   24    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   25    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   26    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   27    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   29    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   30    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   31    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   32    POSSIBILITY OF SUCH DAMAGE.
   33*/
   34
   35:- module(rdf_write,
   36          [ rdf_write_xml/2             % +Stream, +Triples
   37          ]).   38:- use_module(library('semweb/rdf_db')).   39:- use_module(library(lists)).   40:- use_module(library(sgml)).   41:- use_module(library(sgml_write)).   42:- use_module(library(assoc)).   43:- use_module(library(debug)).   44
   45
   46/** <module> Write RDF/XML from a list of triples
   47
   48This module writes an RDF/XML document  from   a  list of triples of the
   49format rdf(Subject, Predicate, Object).  It   is  primarily intended for
   50communicating computed RDF model fragments   to  external programs using
   51RDF/XML.
   52
   53When used from the HTTP library, use the following code:
   54
   55==
   56reply_graph(RDF) :-
   57        format('Content-type: application/rdf+xml; charset=UTF-8~n~n'),
   58        rdf_write_xml(current_output, RDF).
   59==
   60
   61@author Jan Wielemaker
   62@see    library(semweb/rdf_db) offers saving a named graph directly from
   63        the RDF database.
   64*/
   65
   66
   67                 /*******************************
   68                 *           WRITE RDFXML       *
   69                 *******************************/
   70
   71%!  rdf_write_xml(+Out:stream, +Triples:list(rdf(S,P,O))) is det.
   72%
   73%   Write an RDF/XML serialization of Triples to Out.
   74
   75rdf_write_xml(Out, Triples) :-
   76    sort(Triples, Unique),
   77    rdf_write_header(Out, Unique),
   78    node_id_map(Unique, AnonIDs),
   79    rdf_write_triples(Unique, AnonIDs, Out),
   80    rdf_write_footer(Out).
   81
   82
   83                 /*******************************
   84                 *        HEADER/FOOTER         *
   85                 *******************************/
   86
   87%!  rdf_write_header(+Out, +Triples)
   88%
   89%   Save XML document header, doctype and open the RDF environment.
   90%   This predicate also sets up the namespace notation.
   91
   92rdf_write_header(Out, Triples) :-
   93    xml_encoding(Out, Enc, Encoding),
   94    format(Out, '<?xml version=\'1.0\' encoding=\'~w\'?>~n', [Encoding]),
   95    format(Out, '<!DOCTYPE rdf:RDF [', []),
   96    used_namespaces(Triples, NSList),
   97    (   member(Id, NSList),
   98        ns(Id, NS),
   99        xml_quote_attribute(NS, NSText0, Enc),
  100        xml_escape_parameter_entity(NSText0, NSText),
  101        format(Out, '~N    <!ENTITY ~w \'~w\'>', [Id, NSText]),
  102        fail
  103    ;   true
  104    ),
  105    format(Out, '~N]>~n~n', []),
  106    format(Out, '<rdf:RDF', []),
  107    (   member(Id, NSList),
  108        format(Out, '~N    xmlns:~w="&~w;"~n', [Id, Id]),
  109        fail
  110    ;   true
  111    ),
  112    format(Out, '>~n', []).
  113
  114
  115xml_encoding(Out, Enc, Encoding) :-
  116    stream_property(Out, encoding(Enc)),
  117    (   xml_encoding_name(Enc, Encoding)
  118    ->  true
  119    ;   throw(error(domain_error(rdf_encoding, Enc), _))
  120    ).
  121
  122xml_encoding_name(ascii,       'US-ASCII').
  123xml_encoding_name(iso_latin_1, 'ISO-8859-1').
  124xml_encoding_name(utf8,        'UTF-8').
  125
  126%!  xml_escape_parameter_entity(+In, -Out) is det.
  127%
  128%   Escape % as &#37; for entity declarations.
  129
  130xml_escape_parameter_entity(In, Out) :-
  131    sub_atom(In, _, _, _, '%'),
  132    !,
  133    atom_codes(In, Codes),
  134    phrase(escape_parent(Codes), OutCodes),
  135    atom_codes(Out, OutCodes).
  136xml_escape_parameter_entity(In, In).
  137
  138escape_parent([]) --> [].
  139escape_parent([H|T]) -->
  140    (   { H == 37 }
  141    ->  "&#37;"
  142    ;   [H]
  143    ),
  144    escape_parent(T).
  145
  146%!  used_namespaces(+Triples:list(rdf(S,P,O)), -List:atom) is det.
  147%
  148%   Return the list of namespace abbreviations used in a set of
  149%   triples.
  150
  151used_namespaces(Triples, NSList) :-
  152    decl_used_predicate_ns(Triples),
  153    resources(Triples, Resources),
  154    empty_assoc(A0),
  155    put_assoc(rdf, A0, *, A1),      % needed for rdf:RDF
  156    res_used_namespaces(Resources, _NoNS, A1, A),
  157    assoc_to_keys(A, NSList).
  158
  159
  160res_used_namespaces([], [], A, A).
  161res_used_namespaces([Resource|T], NoNS, A0, A) :-
  162    ns(NS, Full),
  163    Full \== '',
  164    atom_concat(Full, Local, Resource),
  165    xml_name(Local),
  166    !,
  167    put_assoc(NS, A0, *, A1),
  168    res_used_namespaces(T, NoNS, A1, A).
  169res_used_namespaces([R|T0], [R|T], A0, A) :-
  170    res_used_namespaces(T0, T, A0, A).
  171
  172%!  resources(+Triples:list(rdf(S,P,O)), -Resources:list(atom)) is det.
  173%
  174%   Resources is the set of resources referenced in Triples.
  175
  176resources(Triples, Resources) :-
  177    phrase(resources(Triples), Raw),
  178    sort(Raw, Resources).
  179
  180resources([]) -->
  181    [].
  182resources([rdf(S,P,O)|T]) -->
  183    [S,P],
  184    object_resources(O),
  185    resources(T).
  186
  187object_resources(Atom) -->
  188    { atom(Atom) },
  189    !,
  190    [ Atom ].
  191object_resources(literal(type(Type, _))) -->
  192    !,
  193    [ Type ].
  194object_resources(_) -->
  195    [].
  196
  197%!  decl_used_predicate_ns(+Triples:list(rdf(S,P,O)))
  198%
  199%   For every URL used as a predicate   we *MUST* define a namespace
  200%   as we cannot use names holding /, :, etc. as XML identifiers.
  201
  202:- thread_local
  203    predicate_ns/2.  204
  205decl_used_predicate_ns(Triples) :-
  206    retractall(predicate_ns(_,_)),
  207    (   member(rdf(_,P,_), Triples),
  208        decl_predicate_ns(P),
  209        fail
  210    ;   true
  211    ).
  212
  213decl_predicate_ns(Pred) :-
  214    predicate_ns(Pred, _),
  215    !.
  216decl_predicate_ns(Pred) :-
  217    rdf_global_id(NS:Local, Pred),
  218    xml_name(Local),
  219    !,
  220    assert(predicate_ns(Pred, NS)).
  221decl_predicate_ns(Pred) :-
  222    is_bag_li_predicate(Pred),
  223    !.
  224decl_predicate_ns(Pred) :-
  225    atom_codes(Pred, Codes),
  226    append(NSCodes, LocalCodes, Codes),
  227    xml_codes(LocalCodes),
  228    !,
  229    (   NSCodes \== []
  230    ->  atom_codes(NS, NSCodes),
  231        (   ns(Id, NS)
  232        ->  assert(predicate_ns(Pred, Id))
  233        ;   between(1, infinite, N),
  234            atom_concat(ns, N, Id),
  235            \+ ns(Id, _)
  236        ->  rdf_register_ns(Id, NS),
  237            print_message(informational,
  238                          rdf(using_namespace(Id, NS)))
  239        ),
  240        assert(predicate_ns(Pred, Id))
  241    ;   assert(predicate_ns(Pred, -)) % no namespace used
  242    ).
  243
  244xml_codes([]).
  245xml_codes([H|T]) :-
  246    xml_code(H),
  247    xml_codes(T).
  248
  249xml_code(X) :-
  250    code_type(X, csym),
  251    !.
  252xml_code(0'-).                          % '
  253
  254
  255rdf_write_footer(Out) :-
  256    format(Out, '</rdf:RDF>~n', []).
  257
  258
  259                 /*******************************
  260                 *          ANONYMOUS IDS       *
  261                 *******************************/
  262
  263%!  node_id_map(+Triples, -IdMap) is det.
  264%
  265%   Create an assoc Resource -> NodeID for those anonymous resources
  266%   in Triples that need  a  NodeID.   This  implies  all  anonymous
  267%   resources that are used multiple times as object value.
  268
  269node_id_map(Triples, IdMap) :-
  270    anonymous_objects(Triples, Objs),
  271    msort(Objs, Sorted),
  272    empty_assoc(IdMap0),
  273    nodeid_map(Sorted, 0, IdMap0, IdMap).
  274
  275anonymous_objects([], []).
  276anonymous_objects([rdf(_,_,O)|T0], Anon) :-
  277    rdf_is_bnode(O),
  278    !,
  279    Anon = [O|T],
  280    anonymous_objects(T0, T).
  281anonymous_objects([_|T0], T) :-
  282    anonymous_objects(T0, T).
  283
  284nodeid_map([], _, Map, Map).
  285nodeid_map([H,H|T0], Id, Map0, Map) :-
  286    !,
  287    remove_leading(H, T0, T),
  288    atom_concat(bn, Id, NodeId),
  289    put_assoc(H, Map0, NodeId, Map1),
  290    Id2 is Id + 1,
  291    nodeid_map(T, Id2, Map1, Map).
  292nodeid_map([_|T], Id, Map0, Map) :-
  293    nodeid_map(T, Id, Map0, Map).
  294
  295remove_leading(H, [H|T0], T) :-
  296    !,
  297    remove_leading(H, T0, T).
  298remove_leading(_, T, T).
  299
  300
  301                 /*******************************
  302                 *            TRIPLES           *
  303                 *******************************/
  304
  305rdf_write_triples(Triples, NodeIDs, Out) :-
  306    rdf_write_triples(Triples, NodeIDs, Out, [], Anon),
  307    rdf_write_anon(Anon, NodeIDs, Out, Anon).
  308
  309rdf_write_triples([], _, _, Anon, Anon).
  310rdf_write_triples([H|T0], NodeIDs, Out, Anon0, Anon) :-
  311    arg(1, H, S),
  312    subject_triples(S, [H|T0], T, OnSubject),
  313    (   rdf_is_bnode(S)
  314    ->  rdf_write_triples(T, NodeIDs, Out, [anon(S,_,OnSubject)|Anon0], Anon)
  315    ;   rdf_write_subject(OnSubject, S, NodeIDs, Out, Anon0),
  316        rdf_write_triples(T, NodeIDs, Out, Anon0, Anon)
  317    ).
  318
  319subject_triples(S, [H|T0], T, [H|M]) :-
  320    arg(1, H, S),
  321    !,
  322    subject_triples(S, T0, T, M).
  323subject_triples(_, T, T, []).
  324
  325
  326rdf_write_anon([], _, _, _).
  327rdf_write_anon([anon(Subject, Done, Triples)|T], NodeIDs, Out, Anon) :-
  328    Done \== true,
  329    !,
  330    Done = true,
  331    rdf_write_subject(Triples, Subject, NodeIDs, Out, Anon),
  332    rdf_write_anon(T, NodeIDs, Out, Anon).
  333rdf_write_anon([_|T], NodeIDs, Out, Anon) :-
  334    rdf_write_anon(T, NodeIDs, Out, Anon).
  335
  336rdf_write_subject(Triples, Subject, NodeIDs, Out, Anon) :-
  337    rdf_write_subject(Triples, Out, Subject, NodeIDs, -, 0, Anon),
  338    !,
  339    format(Out, '~n', []).
  340rdf_write_subject(_, Subject, _, _, _) :-
  341    throw(error(rdf_save_failed(Subject), 'Internal error')).
  342
  343rdf_write_subject(Triples, Out, Subject, NodeIDs, DefNS, Indent, Anon) :-
  344    rdf_equal(rdf:type, RdfType),
  345    select(rdf(_, RdfType,Type), Triples, Triples1),
  346    \+ rdf_is_bnode(Type),
  347    rdf_id(Type, DefNS, TypeId),
  348    xml_is_name(TypeId),
  349    !,
  350    format(Out, '~*|<', [Indent]),
  351    rdf_write_id(Out, TypeId),
  352    save_about(Out, Subject, NodeIDs),
  353    save_attributes(Triples1, DefNS, Out, NodeIDs, TypeId, Indent, Anon).
  354rdf_write_subject(Triples, Out, Subject, NodeIDs, _DefNS, Indent, Anon) :-
  355    format(Out, '~*|<rdf:Description', [Indent]),
  356    save_about(Out, Subject, NodeIDs),
  357    save_attributes(Triples, rdf, Out, NodeIDs, rdf:'Description', Indent, Anon).
  358
  359xml_is_name(_NS:Atom) :-
  360    !,
  361    xml_name(Atom).
  362xml_is_name(Atom) :-
  363    xml_name(Atom).
  364
  365save_about(Out, Subject, NodeIDs) :-
  366    rdf_is_bnode(Subject),
  367    !,
  368    (   get_assoc(Subject, NodeIDs, NodeID)
  369    ->  format(Out,' rdf:nodeID="~w"', [NodeID])
  370    ;   true
  371    ).
  372save_about(Out, Subject, _) :-
  373    stream_property(Out, encoding(Encoding)),
  374    rdf_value(Subject, QSubject, Encoding),
  375    format(Out, ' rdf:about="~w"', [QSubject]),
  376    !.
  377save_about(_, _, _) :-
  378    assertion(fail).
  379
  380%!  save_attributes(+List, +DefNS, +Out, +NodeIDs, Element, +Indent, +Anon)
  381%
  382%   Save the attributes.  Short literal attributes are saved in the
  383%   tag.  Others as the content of the description element.  The
  384%   begin tag has already been filled.
  385
  386save_attributes(Triples, DefNS, Out, NodeIDs, Element, Indent, Anon) :-
  387    split_attributes(Triples, InTag, InBody),
  388    SubIndent is Indent + 2,
  389    save_attributes2(InTag, DefNS, tag, Out, NodeIDs, SubIndent, Anon),
  390    (   InBody == []
  391    ->  format(Out, '/>~n', [])
  392    ;   format(Out, '>~n', []),
  393        save_attributes2(InBody, _, body, Out, NodeIDs, SubIndent, Anon),
  394        format(Out, '~N~*|</~w>~n', [Indent, Element])
  395    ).
  396
  397%       split_attributes(+Triples, -HeadAttrs, -BodyAttr)
  398%
  399%       Split attribute (Name=Value) list into attributes for the head
  400%       and body. Attributes can only be in the head if they are literal
  401%       and appear only one time in the attribute list.
  402
  403split_attributes(Triples, HeadAttr, BodyAttr) :-
  404    duplicate_attributes(Triples, Dupls, Singles),
  405    simple_literal_attributes(Singles, HeadAttr, Rest),
  406    append(Dupls, Rest, BodyAttr).
  407
  408%       duplicate_attributes(+Attrs, -Duplicates, -Singles)
  409%
  410%       Extract attributes that appear more than onces as we cannot
  411%       dublicate an attribute in the head according to the XML rules.
  412
  413duplicate_attributes([], [], []).
  414duplicate_attributes([H|T], Dupls, Singles) :-
  415    arg(2, H, Name),
  416    named_attributes(Name, T, D, R),
  417    D \== [],
  418    append([H|D], Dupls2, Dupls),
  419    !,
  420    duplicate_attributes(R, Dupls2, Singles).
  421duplicate_attributes([H|T], Dupls2, [H|Singles]) :-
  422    duplicate_attributes(T, Dupls2, Singles).
  423
  424named_attributes(_, [], [], []) :- !.
  425named_attributes(Name, [H|T], D, R) :-
  426    (   arg(2, H, Name)
  427    ->  D = [H|DT],
  428        named_attributes(Name, T, DT, R)
  429    ;   R = [H|RT],
  430        named_attributes(Name, T, D, RT)
  431    ).
  432
  433%       simple_literal_attributes(+Attributes, -Inline, -Body)
  434%
  435%       Split attributes for (literal) attributes to be used in the
  436%       begin-tag and ones that have to go into the body of the description.
  437
  438simple_literal_attributes([], [], []).
  439simple_literal_attributes([H|TA], [H|TI], B) :-
  440    in_tag_attribute(H),
  441    !,
  442    simple_literal_attributes(TA, TI, B).
  443simple_literal_attributes([H|TA], I, [H|TB]) :-
  444    simple_literal_attributes(TA, I, TB).
  445
  446in_tag_attribute(rdf(_,P,literal(Text))) :-
  447    atom(Text),                     % may not have lang qualifier
  448    atom_length(Text, Len),
  449    Len < 60,
  450    \+ is_bag_li_predicate(P).
  451
  452
  453%       save_attributes(+List, +DefNS, +TagOrBody, +Out, +NodeIDs, +Indent, +Anon)
  454%
  455%       Save a list of attributes.
  456
  457save_attributes2([], _, _, _, _, _, _).
  458save_attributes2([H|T], DefNS, Where, Out, NodeIDs, Indent, Anon) :-
  459    save_attribute(Where, H, DefNS, Out, NodeIDs, Indent, Anon),
  460    save_attributes2(T, DefNS, Where, Out, NodeIDs, Indent, Anon).
  461
  462%!  save_attribute(+Where, +Triple, +DefNS, +Out, +NodeIDs, +Indent, +Anon)
  463
  464save_attribute(tag, rdf(_, Name, literal(Value)), DefNS, Out, _, Indent, _Anon) :-
  465    AttIndent is Indent + 2,
  466    rdf_att_id(Name, DefNS, NameText),
  467    stream_property(Out, encoding(Encoding)),
  468    xml_quote_attribute(Value, QVal, Encoding),
  469    format(Out, '~N~*|', [AttIndent]),
  470    rdf_write_id(Out, NameText),
  471    format(Out, '="~w"', [QVal]).
  472save_attribute(body, rdf(_,Name,literal(Literal)), DefNS, Out, _, Indent, _) :-
  473    !,
  474    rdf_p_id(Name, DefNS, NameText),
  475    format(Out, '~N~*|<', [Indent]),
  476    rdf_write_id(Out, NameText),
  477    (   Literal = lang(Lang, Value)
  478    ->  rdf_id(Lang, DefNS, LangText),
  479        format(Out, ' xml:lang="~w">', [LangText])
  480    ;   Literal = type(Type, Value)
  481    ->  (   rdf_equal(Type, rdf:'XMLLiteral')
  482        ->  write(Out, ' rdf:parseType="Literal">'),
  483            Value = Literal
  484        ;   stream_property(Out, encoding(Encoding)),
  485            rdf_value(Type, QVal, Encoding),
  486            format(Out, ' rdf:datatype="~w">', [QVal])
  487        )
  488    ;   atomic(Literal)
  489    ->  write(Out, '>'),
  490        Value = Literal
  491    ;   write(Out, ' rdf:parseType="Literal">'),
  492        Value = Literal
  493    ),
  494    save_attribute_value(Value, Out, Indent),
  495    write(Out, '</'), rdf_write_id(Out, NameText), write(Out, '>').
  496save_attribute(body, rdf(_, Name, Value), DefNS, Out, NodeIDs, Indent, Anon) :-
  497    rdf_is_bnode(Value),
  498    !,
  499    (   memberchk(anon(Value, Done, ValueTriples), Anon)
  500    ->  true
  501    ;   ValueTriples = []
  502    ),
  503    rdf_p_id(Name, DefNS, NameText),
  504    format(Out, '~N~*|<', [Indent]),
  505    rdf_write_id(Out, NameText),
  506    (   var(Done)
  507    ->  Done = true,
  508        SubIndent is Indent + 2,
  509        (   rdf_equal(RdfType, rdf:type),
  510            rdf_equal(ListClass, rdf:'List'),
  511            memberchk(rdf(_, RdfType, ListClass), ValueTriples)
  512        ->  format(Out, ' rdf:parseType="Collection">~n', []),
  513            rdf_save_list(ValueTriples, Out, Value, NodeIDs, DefNS, SubIndent, Anon)
  514        ;   format(Out, '>~n', []),
  515            rdf_write_subject(ValueTriples, Out, Value, NodeIDs, DefNS, SubIndent, Anon)
  516        ),
  517        format(Out, '~N~*|</', [Indent]),
  518        rdf_write_id(Out, NameText),
  519        format(Out, '>~n', [])
  520    ;   get_assoc(Value, NodeIDs, NodeID)
  521    ->  format(Out, ' rdf:nodeID="~w"/>', [NodeID])
  522    ;   assertion(fail)
  523    ).
  524save_attribute(body, rdf(_, Name, Value), DefNS, Out, _, Indent, _Anon) :-
  525    stream_property(Out, encoding(Encoding)),
  526    rdf_value(Value, QVal, Encoding),
  527    rdf_p_id(Name, DefNS, NameText),
  528    format(Out, '~N~*|<', [Indent]),
  529    rdf_write_id(Out, NameText),
  530    format(Out, ' rdf:resource="~w"/>', [QVal]).
  531
  532save_attribute_value(Value, Out, _) :-  % strings
  533    atom(Value),
  534    !,
  535    stream_property(Out, encoding(Encoding)),
  536    xml_quote_cdata(Value, QVal, Encoding),
  537    write(Out, QVal).
  538save_attribute_value(Value, Out, _) :-  % numbers
  539    number(Value),
  540    !,
  541    writeq(Out, Value).             % quoted: preserve floats
  542save_attribute_value(Value, Out, Indent) :-
  543    xml_is_dom(Value),
  544    !,
  545    XMLIndent is Indent+2,
  546    xml_write(Out, Value,
  547              [ header(false),
  548                indent(XMLIndent)
  549              ]).
  550save_attribute_value(Value, _Out, _) :-
  551    throw(error(save_attribute_value(Value), _)).
  552
  553rdf_save_list(_, _, List, _, _, _, _) :-
  554    rdf_equal(List, rdf:nil),
  555    !.
  556rdf_save_list(ListTriples, Out, List, NodeIDs, DefNS, Indent, Anon) :-
  557    rdf_equal(RdfFirst, rdf:first),
  558    memberchk(rdf(List, RdfFirst, First), ListTriples),
  559    (   rdf_is_bnode(First),
  560        memberchk(anon(First, true, FirstTriples), Anon)
  561    ->  nl(Out),
  562        rdf_write_subject(FirstTriples, Out, First, NodeIDs, DefNS, Indent, Anon)
  563    ;   stream_property(Out, encoding(Encoding)),
  564        rdf_value(First, QVal, Encoding),
  565        format(Out, '~N~*|<rdf:Description about="~w"/>',
  566               [Indent, QVal])
  567    ),
  568    (   rdf_equal(RdfRest, rdf:rest),
  569        memberchk(rdf(List, RdfRest, List2), ListTriples),
  570        \+ rdf_equal(List2, rdf:nil),
  571        memberchk(anon(List2, true, List2Triples), Anon)
  572    ->  rdf_save_list(List2Triples, Out, List2, NodeIDs, DefNS, Indent, Anon)
  573    ;   true
  574    ).
  575
  576%!  rdf_p_id(+Resource, +DefNS, -NSLocal)
  577%
  578%   As rdf_id/3 for predicate names.  Maps _:<N> to rdf:li.
  579%
  580%   @tbd    Ensure we are talking about an rdf:Bag
  581
  582rdf_p_id(LI, _, 'rdf:li') :-
  583    is_bag_li_predicate(LI),
  584    !.
  585rdf_p_id(Resource, DefNS, NSLocal) :-
  586    rdf_id(Resource, DefNS, NSLocal).
  587
  588%!  is_bag_li_predicate(+Pred) is semidet.
  589%
  590%   True if Pred is _:N, as used  for members of an rdf:Bag, rdf:Seq
  591%   or rdf:Alt.
  592
  593is_bag_li_predicate(Pred) :-
  594    atom_concat('_:', AN, Pred),
  595    catch(atom_number(AN, N), _, true), integer(N), N >= 0,
  596    !.
  597
  598
  599%!  rdf_id(+Resource, +DefNS, -NSLocal)
  600%
  601%   Generate a NS:Local name for Resource given the indicated
  602%   default namespace.  This call is used for elements.
  603
  604rdf_id(Id, NS, NS:Local) :-
  605    ns(NS, Full),
  606    Full \== '',
  607    atom_concat(Full, Local, Id),
  608    xml_name(Local),
  609    !.
  610rdf_id(Id, _, NS:Local) :-
  611    ns(NS, Full),
  612    Full \== '',
  613    atom_concat(Full, Local, Id),
  614    xml_name(Local),
  615    !.
  616rdf_id(Id, _, Id).
  617
  618
  619%!  rdf_write_id(+Out, +NSLocal) is det.
  620%
  621%   Write an identifier. We cannot use native write on it as both NS
  622%   and Local can be operators.
  623
  624rdf_write_id(Out, NS:Local) :-
  625    !,
  626    format(Out, '~w:~w', [NS, Local]).
  627rdf_write_id(Out, Atom) :-
  628    write(Out, Atom).
  629
  630
  631%!  rdf_att_id(+URI, +DefNS, -ID)
  632
  633rdf_att_id(Id, _, NS:Local) :-
  634    ns(NS, Full),
  635    Full \== '',
  636    atom_concat(Full, Local, Id),
  637    xml_name(Local),
  638    !.
  639rdf_att_id(Id, _, Id).
  640
  641
  642%!  rdf_value(+Resource, -Text, +Encoding)
  643%
  644%   According  to  "6.4  RDF  URI  References"  of  the  RDF  Syntax
  645%   specification, a URI reference is  UNICODE string not containing
  646%   control sequences, represented as  UTF-8   and  then  as escaped
  647%   US-ASCII.
  648%
  649%   NOTE: the to_be_described/1 trick  ensures   entity  rewrite  in
  650%   resources that start with 'http://t-d-b.org?'. This   is  a of a
  651%   hack to save the artchive data   in  the MultimediaN project. We
  652%   should use a more general mechanism.
  653
  654rdf_value(V, Text, Encoding) :-
  655    to_be_described(Prefix),
  656    atom_concat(Prefix, V1, V),
  657    ns(NS, Full),
  658    atom_concat(Full, Local, V1),
  659    !,
  660    xml_quote_attribute(Local, QLocal, Encoding),
  661    atomic_list_concat([Prefix, '&', NS, (';'), QLocal], Text).
  662rdf_value(V, Text, Encoding) :-
  663    ns(NS, Full),
  664    atom_concat(Full, Local, V),
  665    !,
  666    xml_quote_attribute(Local, QLocal, Encoding),
  667    atomic_list_concat(['&', NS, (';'), QLocal], Text).
  668rdf_value(V, Q, Encoding) :-
  669    xml_quote_attribute(V, Q, Encoding).
  670
  671to_be_described('http://t-d-b.org?').
  672
  673
  674                 /*******************************
  675                 *             UTIL             *
  676                 *******************************/
  677
  678ns(Id, Full) :-
  679    rdf_db:ns(Id, Full)