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