1/* Part of SWI-Prolog 2 3 Author: Jan Wielemaker and Anjo Anjewierden 4 E-mail: J.Wielemaker@vu.nl 5 WWW: http://www.swi-prolog.org 6 Copyright (c) 2002-2020, University of Amsterdam 7 VU University Amsterdam 8 All rights reserved. 9 10 Redistribution and use in source and binary forms, with or without 11 modification, are permitted provided that the following conditions 12 are met: 13 14 1. Redistributions of source code must retain the above copyright 15 notice, this list of conditions and the following disclaimer. 16 17 2. Redistributions in binary form must reproduce the above copyright 18 notice, this list of conditions and the following disclaimer in 19 the documentation and/or other materials provided with the 20 distribution. 21 22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 POSSIBILITY OF SUCH DAMAGE. 34*/ 35 36:- module(html_write, 37 [ reply_html_page/2, % :Head, :Body 38 reply_html_page/3, % +Style, :Head, :Body 39 40 % Basic output routines 41 page//1, % :Content 42 page//2, % :Head, :Body 43 page//3, % +Style, :Head, :Body 44 html//1, % :Content 45 46 % Option processing 47 html_set_options/1, % +OptionList 48 html_current_option/1, % ?Option 49 50 % repositioning HTML elements 51 html_post//2, % +Id, :Content 52 html_receive//1, % +Id 53 html_receive//2, % +Id, :Handler 54 xhtml_ns//2, % +Id, +Value 55 html_root_attribute//2, % +Name, +Value 56 57 html/4, % {|html||quasi quotations|} 58 59 % Useful primitives for expanding 60 html_begin//1, % +EnvName[(Attribute...)] 61 html_end//1, % +EnvName 62 html_quoted//1, % +Text 63 html_quoted_attribute//1, % +Attribute 64 65 % Emitting the HTML code 66 print_html/1, % +List 67 print_html/2, % +Stream, +List 68 html_print_length/2, % +List, -Length 69 70 % Extension support 71 (html_meta)/1, % +Spec 72 op(1150, fx, html_meta) 73 ]). 74:- use_module(html_quasiquotations, [html/4]). 75:- autoload(library(apply),[maplist/3,maplist/4]). 76:- autoload(library(debug),[debug/3]). 77:- autoload(library(error), 78 [must_be/2,domain_error/2,instantiation_error/1]). 79:- autoload(library(lists), 80 [permutation/2,selectchk/3,append/3,select/4,list_to_set/2]). 81:- autoload(library(option),[option/2]). 82:- autoload(library(pairs),[group_pairs_by_key/2]). 83:- autoload(library(sgml),[xml_quote_cdata/3,xml_quote_attribute/3]). 84:- autoload(library(uri),[uri_encoded/3]). 85:- autoload(library(url),[www_form_encode/2]). 86:- if(exists_source(library(http/http_dispatch))). 87:- autoload(library(http/http_dispatch), [http_location_by_id/2]). 88:- endif. 89 90% Quote output 91:- set_prolog_flag(generate_debug_info, false). 92 93:- meta_predicate 94 reply_html_page( , , ), 95 reply_html_page( , ), 96 html( , , ), 97 page( , , ), 98 page( , , , ), 99 pagehead( , , , ), 100 pagebody( , , , ), 101 html_receive( , , , ), 102 html_post( , , , ). 103 104:- multifile 105 expand//1, % +HTMLElement 106 expand_attribute_value//1, % +HTMLAttributeValue 107 html_header_hook/1. % +Style
144 /******************************* 145 * SETTINGS * 146 *******************************/
html4
, xhtml
or html5
(default). For
compatibility reasons, html
is accepted as an
alias for html4
.<|DOCTYPE
DocType >
line for page//1 and
page//2.Content-type
for reply_html_page/3
Note that the doctype and content_type flags are covered by
distinct prolog flags: html4_doctype
, xhtml_doctype
and
html5_doctype
and similar for the content type. The Dialect
must be switched before doctype and content type.
172html_set_options(Options) :- 173 must_be(list, Options), 174 set_options(Options). 175 176set_options([]). 177set_options([H|T]) :- 178 html_set_option(H), 179 set_options(T). 180 181html_set_option(dialect(Dialect0)) :- 182 !, 183 must_be(oneof([html,html4,xhtml,html5]), Dialect0), 184 ( html_version_alias(Dialect0, Dialect) 185 -> true 186 ; Dialect = Dialect0 187 ), 188 set_prolog_flag(html_dialect, Dialect). 189html_set_option(doctype(Atom)) :- 190 !, 191 must_be(atom, Atom), 192 current_prolog_flag(html_dialect, Dialect), 193 dialect_doctype_flag(Dialect, Flag), 194 set_prolog_flag(Flag, Atom). 195html_set_option(content_type(Atom)) :- 196 !, 197 must_be(atom, Atom), 198 current_prolog_flag(html_dialect, Dialect), 199 dialect_content_type_flag(Dialect, Flag), 200 set_prolog_flag(Flag, Atom). 201html_set_option(O) :- 202 domain_error(html_option, O). 203 204html_version_alias(html, html4).
210html_current_option(dialect(Dialect)) :- 211 current_prolog_flag(html_dialect, Dialect). 212html_current_option(doctype(DocType)) :- 213 current_prolog_flag(html_dialect, Dialect), 214 dialect_doctype_flag(Dialect, Flag), 215 current_prolog_flag(Flag, DocType). 216html_current_option(content_type(ContentType)) :- 217 current_prolog_flag(html_dialect, Dialect), 218 dialect_content_type_flag(Dialect, Flag), 219 current_prolog_flag(Flag, ContentType). 220 221dialect_doctype_flag(html4, html4_doctype). 222dialect_doctype_flag(html5, html5_doctype). 223dialect_doctype_flag(xhtml, xhtml_doctype). 224 225dialect_content_type_flag(html4, html4_content_type). 226dialect_content_type_flag(html5, html5_content_type). 227dialect_content_type_flag(xhtml, xhtml_content_type). 228 229option_default(html_dialect, html5). 230option_default(html4_doctype, 231 'HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" \c 232 "http://www.w3.org/TR/html4/loose.dtd"'). 233option_default(html5_doctype, 234 'html'). 235option_default(xhtml_doctype, 236 'html PUBLIC "-//W3C//DTD XHTML 1.0 \c 237 Transitional//EN" \c 238 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"'). 239option_default(html4_content_type, 'text/html; charset=UTF-8'). 240option_default(html5_content_type, 'text/html; charset=UTF-8'). 241option_default(xhtml_content_type, 'application/xhtml+xml; charset=UTF-8').
247init_options :- 248 ( option_default(Name, Value), 249 ( current_prolog_flag(Name, _) 250 -> true 251 ; create_prolog_flag(Name, Value, []) 252 ), 253 fail 254 ; true 255 ). 256 257:- init_options.
263xml_header('<?xml version=\'1.0\' encoding=\'UTF-8\'?>').
269ns(xhtml, 'http://www.w3.org/1999/xhtml'). 270 271 272 /******************************* 273 * PAGE * 274 *******************************/
<!DOCTYPE>
header. The
actual doctype is read from the option doctype
as defined by
html_set_options/1.283page(Content) --> 284 doctype, 285 html(html(Content)). 286 287page(Head, Body) --> 288 page(default, Head, Body). 289 290page(Style, Head, Body) --> 291 doctype, 292 content_type, 293 html_begin(html), 294 pagehead(Style, Head), 295 pagebody(Style, Body), 296 html_end(html).
<DOCTYPE ...
header. The doctype comes from the
option doctype(DOCTYPE)
(see html_set_options/1). Setting the
doctype to '' (empty atom) suppresses the header completely.
This is to avoid a IE bug in processing AJAX output ...305doctype --> 306 { html_current_option(doctype(DocType)), 307 DocType \== '' 308 }, 309 !, 310 [ '<!DOCTYPE ', DocType, '>' ]. 311doctype --> 312 []. 313 314content_type --> 315 { html_current_option(content_type(Type)) 316 }, 317 !, 318 html_post(head, meta([ 'http-equiv'('content-type'), 319 content(Type) 320 ], [])). 321content_type --> 322 { html_current_option(dialect(html5)) }, 323 !, 324 html_post(head, meta('charset=UTF-8')). 325content_type --> 326 []. 327 328pagehead(_, Head) --> 329 { functor(Head, head, _) 330 }, 331 !, 332 html(Head). 333pagehead(Style, Head) --> 334 { strip_module(Head, M, _), 335 hook_module(M, HM, head//2) 336 }, 337 HM:head(Style, Head), 338 !. 339pagehead(_, Head) --> 340 { strip_module(Head, M, _), 341 hook_module(M, HM, head//1) 342 }, 343 HM:head(Head), 344 !. 345pagehead(_, Head) --> 346 html(head(Head)). 347 348 349pagebody(_, Body) --> 350 { functor(Body, body, _) 351 }, 352 !, 353 html(Body). 354pagebody(Style, Body) --> 355 { strip_module(Body, M, _), 356 hook_module(M, HM, body//2) 357 }, 358 HM:body(Style, Body), 359 !. 360pagebody(_, Body) --> 361 { strip_module(Body, M, _), 362 hook_module(M, HM, body//1) 363 }, 364 HM:body(Body), 365 !. 366pagebody(_, Body) --> 367 html(body(Body)). 368 369 370hook_module(M, M, PI) :- 371 current_predicate(M:PI), 372 !. 373hook_module(_, user, PI) :- 374 current_predicate(user:PI).
381html(Spec) --> 382 { strip_module(Spec, M, T) }, 383 qhtml(T, M). 384 385qhtml(Var, _) --> 386 { var(Var), 387 !, 388 instantiation_error(Var) 389 }. 390qhtml([], _) --> 391 !, 392 []. 393qhtml([H|T], M) --> 394 !, 395 html_expand(H, M), 396 qhtml(T, M). 397qhtml(X, M) --> 398 html_expand(X, M). 399 400html_expand(Var, _) --> 401 { var(Var), 402 !, 403 instantiation_error(Var) 404 }. 405html_expand(Term, Module) --> 406 do_expand(Term, Module), 407 !. 408html_expand(Term, _Module) --> 409 { print_message(error, html(expand_failed(Term))) }. 410 411 412do_expand(Token, _) --> % call user hooks 413 expand(Token), 414 !. 415do_expand(Fmt-Args, _) --> 416 !, 417 { format(string(String), Fmt, Args) 418 }, 419 html_quoted(String). 420do_expand(\List, Module) --> 421 { is_list(List) 422 }, 423 !, 424 raw(List, Module). 425do_expand(\Term, Module, In, Rest) :- 426 !, 427 call(Module:Term, In, Rest). 428do_expand(Module:Term, _) --> 429 !, 430 qhtml(Term, Module). 431do_expand(&(Entity), _) --> 432 !, 433 { integer(Entity) 434 -> format(string(String), '&#~d;', [Entity]) 435 ; format(string(String), '&~w;', [Entity]) 436 }, 437 [ String ]. 438do_expand(Token, _) --> 439 { atomic(Token) 440 }, 441 !, 442 html_quoted(Token). 443do_expand(element(Env, Attributes, Contents), M) --> 444 !, 445 ( { Contents == [], 446 html_current_option(dialect(xhtml)) 447 } 448 -> xhtml_empty(Env, Attributes) 449 ; html_begin(Env, Attributes), 450 qhtml(Env, Contents, M), 451 html_end(Env) 452 ). 453do_expand(Term, M) --> 454 { Term =.. [Env, Contents] 455 }, 456 !, 457 ( { layout(Env, _, empty) 458 } 459 -> html_begin(Env, Contents) 460 ; ( { Contents == [], 461 html_current_option(dialect(xhtml)) 462 } 463 -> xhtml_empty(Env, []) 464 ; html_begin(Env), 465 qhtml(Env, Contents, M), 466 html_end(Env) 467 ) 468 ). 469do_expand(Term, M) --> 470 { Term =.. [Env, Attributes, Contents], 471 check_non_empty(Contents, Env, Term) 472 }, 473 !, 474 ( { Contents == [], 475 html_current_option(dialect(xhtml)) 476 } 477 -> xhtml_empty(Env, Attributes) 478 ; html_begin(Env, Attributes), 479 qhtml(Env, Contents, M), 480 html_end(Env) 481 ). 482 483qhtml(Env, Contents, M) --> 484 { cdata_element(Env), 485 phrase(cdata(Contents, M), Tokens) 486 }, 487 !, 488 [ cdata(Env, Tokens) ]. 489qhtml(_, Contents, M) --> 490 qhtml(Contents, M). 491 492 493check_non_empty([], _, _) :- !. 494check_non_empty(_, Tag, Term) :- 495 layout(Tag, _, empty), 496 !, 497 print_message(warning, 498 format('Using empty element with content: ~p', [Term])). 499check_non_empty(_, _, _). 500 501cdata(List, M) --> 502 { is_list(List) }, 503 !, 504 raw(List, M). 505cdata(One, M) --> 506 raw_element(One, M).
512raw([], _) --> 513 []. 514raw([H|T], Module) --> 515 raw_element(H, Module), 516 raw(T, Module). 517 518raw_element(Var, _) --> 519 { var(Var), 520 !, 521 instantiation_error(Var) 522 }. 523raw_element(\List, Module) --> 524 { is_list(List) 525 }, 526 !, 527 raw(List, Module). 528raw_element(\Term, Module, In, Rest) :- 529 !, 530 call(Module:Term, In, Rest). 531raw_element(Module:Term, _) --> 532 !, 533 raw_element(Term, Module). 534raw_element(Fmt-Args, _) --> 535 !, 536 { format(string(S), Fmt, Args) }, 537 [S]. 538raw_element(Value, _) --> 539 { must_be(atomic, Value) }, 540 [Value].
html(table(border=1, \table_content))
html_begin(table(border=1) table_content, html_end(table)
561html_begin(Env) --> 562 { Env =.. [Name|Attributes] 563 }, 564 html_begin(Name, Attributes). 565 566html_begin(Env, Attributes) --> 567 pre_open(Env), 568 [<], 569 [Env], 570 attributes(Env, Attributes), 571 ( { layout(Env, _, empty), 572 html_current_option(dialect(xhtml)) 573 } 574 -> ['/>'] 575 ; [>] 576 ), 577 post_open(Env). 578 579html_end(Env) --> % empty element or omited close 580 { layout(Env, _, -), 581 html_current_option(dialect(html)) 582 ; layout(Env, _, empty) 583 }, 584 !, 585 []. 586html_end(Env) --> 587 pre_close(Env), 588 ['</'], 589 [Env], 590 ['>'], 591 post_close(Env).
597xhtml_empty(Env, Attributes) -->
598 pre_open(Env),
599 [<],
600 [Env],
601 attributes(Attributes),
602 ['/>'].
xmlns
channel. Rdfa
(http://www.w3.org/2006/07/SWD/RDFa/syntax/), embedding RDF in
(x)html provides a typical usage scenario where we want to
publish the required namespaces in the header. We can define:
rdf_ns(Id) --> { rdf_global_id(Id:'', Value) }, xhtml_ns(Id, Value).
After which we can use rdf_ns//1 as a normal rule in html//1 to
publish namespaces from library(semweb/rdf_db). Note that this
macro only has effect if the dialect is set to xhtml
. In
html
mode it is silently ignored.
The required xmlns
receiver is installed by html_begin//1
using the html
tag and thus is present in any document that
opens the outer html
environment through this library.
627xhtml_ns(Id, Value) --> 628 { html_current_option(dialect(xhtml)) }, 629 !, 630 html_post(xmlns, \attribute(xmlns:Id=Value)). 631xhtml_ns(_, _) --> 632 [].
html(div(...)), html_root_attribute(lang, en), ...
645html_root_attribute(Name, Value) -->
646 html_post(html_begin, \attribute(Name=Value)).
653attributes(html, L) --> 654 !, 655 ( { html_current_option(dialect(xhtml)) } 656 -> ( { option(xmlns(_), L) } 657 -> attributes(L) 658 ; { ns(xhtml, NS) }, 659 attributes([xmlns(NS)|L]) 660 ), 661 html_receive(xmlns) 662 ; attributes(L), 663 html_noreceive(xmlns) 664 ), 665 html_receive(html_begin). 666attributes(_, L) --> 667 attributes(L). 668 669attributes([]) --> 670 !, 671 []. 672attributes([H|T]) --> 673 !, 674 attribute(H), 675 attributes(T). 676attributes(One) --> 677 attribute(One). 678 679attribute(Name=Value) --> 680 !, 681 [' '], name(Name), [ '="' ], 682 attribute_value(Value), 683 ['"']. 684attribute(NS:Term) --> 685 !, 686 { Term =.. [Name, Value] 687 }, 688 !, 689 attribute((NS:Name)=Value). 690attribute(Term) --> 691 { Term =.. [Name, Value] 692 }, 693 !, 694 attribute(Name=Value). 695attribute(Atom) --> % Value-abbreviated attribute 696 { atom(Atom) 697 }, 698 [ ' ', Atom ]. 699 700name(NS:Name) --> 701 !, 702 [NS, :, Name]. 703name(Name) --> 704 [ Name ].
encode(V)
Emit URL-encoded version of V. See www_form_encode/2.encode(Value1)
&Name2=encode(Value2)
...
The hook expand_attribute_value//1 can be defined to
provide additional `function like' translations. For example,
http_dispatch.pl
defines location_by_id(ID)
to refer to a
location on the current server based on the handler id. See
http_location_by_id/2.
726attribute_value(List) --> 727 { is_list(List) }, 728 !, 729 attribute_value_m(List). 730attribute_value(Value) --> 731 attribute_value_s(Value). 732 733% emit a single attribute value 734 735attribute_value_s(Var) --> 736 { var(Var), 737 !, 738 instantiation_error(Var) 739 }. 740attribute_value_s(A+B) --> 741 !, 742 attribute_value(A), 743 ( { is_list(B) } 744 -> ( { B == [] } 745 -> [] 746 ; [?], search_parameters(B) 747 ) 748 ; attribute_value(B) 749 ). 750attribute_value_s(encode(Value)) --> 751 !, 752 { uri_encoded(query_value, Value, Encoded) }, 753 [ Encoded ]. 754attribute_value_s(Value) --> 755 expand_attribute_value(Value), 756 !. 757attribute_value_s(Fmt-Args) --> 758 !, 759 { format(string(Value), Fmt, Args) }, 760 html_quoted_attribute(Value). 761attribute_value_s(Value) --> 762 html_quoted_attribute(Value). 763 764search_parameters([H|T]) --> 765 search_parameter(H), 766 ( {T == []} 767 -> [] 768 ; ['&'], 769 search_parameters(T) 770 ). 771 772search_parameter(Var) --> 773 { var(Var), 774 !, 775 instantiation_error(Var) 776 }. 777search_parameter(Name=Value) --> 778 { www_form_encode(Value, Encoded) }, 779 [Name, =, Encoded]. 780search_parameter(Term) --> 781 { Term =.. [Name, Value], 782 !, 783 www_form_encode(Value, Encoded) 784 }, 785 [Name, =, Encoded]. 786search_parameter(Term) --> 787 { domain_error(search_parameter, Term) 788 }.
body(class([c1, c2]), Body)
Emits <body class="c1 c2"> ...
800attribute_value_m([]) --> 801 []. 802attribute_value_m([H|T]) --> 803 attribute_value_s(H), 804 ( { T == [] } 805 -> [] 806 ; [' '], 807 attribute_value_m(T) 808 ). 809 810 811 /******************************* 812 * QUOTING RULES * 813 *******************************/
html(b(Text))
828html_quoted(Text) -->
829 { xml_quote_cdata(Text, Quoted, utf8) },
830 [ Quoted ].
841html_quoted_attribute(Text) -->
842 { xml_quote_attribute(Text, Quoted, utf8) },
843 [ Quoted ].
</
needs to be escaped.850cdata_element(script). 851cdata_element(style). 852 853 854 /******************************* 855 * REPOSITIONING HTML * 856 *******************************/
A typical usage scenario is to get required CSS links in the document head in a reusable fashion. First, we define css//1 as:
css(URL) --> html_post(css, link([ type('text/css'), rel('stylesheet'), href(URL) ])).
Next we insert the unique CSS links, in the pagehead using the following call to reply_html_page/2:
reply_html_page([ title(...), \html_receive(css) ], ...)
888html_post(Id, Content) -->
889 { strip_module(Content, M, C) },
890 [ mailbox(Id, post(M, C)) ].
903html_receive(Id) -->
904 html_receive(Id, sorted_html).
phrase(Handler, PostedTerms, HtmlTerms, Rest)
Typically, Handler collects the posted terms, creating a term suitable for html//1 and finally calls html//1.
923html_receive(Id, Handler) -->
924 { strip_module(Handler, M, P) },
925 [ mailbox(Id, accept(M:P, _)) ].
931html_noreceive(Id) -->
932 [ mailbox(Id, ignore(_,_)) ].
head
and script
boxes at
the end.943mailman(Tokens) :- 944 ( html_token(mailbox(_, accept(_, Accepted)), Tokens) 945 -> true 946 ), 947 var(Accepted), % not yet executed 948 !, 949 mailboxes(Tokens, Boxes), 950 keysort(Boxes, Keyed), 951 group_pairs_by_key(Keyed, PerKey), 952 move_last(PerKey, script, PerKey1), 953 move_last(PerKey1, head, PerKey2), 954 ( permutation(PerKey2, PerKeyPerm), 955 ( mail_ids(PerKeyPerm) 956 -> ! 957 ; debug(html(mailman), 958 'Failed mail delivery order; retrying', []), 959 fail 960 ) 961 -> true 962 ; print_message(error, html(cyclic_mailboxes)) 963 ). 964mailman(_). 965 966move_last(Box0, Id, Box) :- 967 selectchk(Id-List, Box0, Box1), 968 !, 969 append(Box1, [Id-List], Box). 970move_last(Box, _, Box).
cdata(Elem, Tokens)
.977html_token(Token, [H|T]) :- 978 html_token_(T, H, Token). 979 980html_token_(_, Token, Token) :- !. 981html_token_(_, cdata(_,Tokens), Token) :- 982 html_token(Token, Tokens). 983html_token_([H|T], _, Token) :- 984 html_token_(T, H, Token).
990mailboxes(Tokens, MailBoxes) :- 991 mailboxes(Tokens, MailBoxes, []). 992 993mailboxes([], List, List). 994mailboxes([mailbox(Id, Value)|T0], [Id-Value|T], Tail) :- 995 !, 996 mailboxes(T0, T, Tail). 997mailboxes([cdata(_Type, Tokens)|T0], Boxes, Tail) :- 998 !, 999 mailboxes(Tokens, Boxes, Tail0), 1000 mailboxes(T0, Tail0, Tail). 1001mailboxes([_|T0], T, Tail) :- 1002 mailboxes(T0, T, Tail). 1003 1004mail_ids([]). 1005mail_ids([H|T0]) :- 1006 mail_id(H, NewPosts), 1007 add_new_posts(NewPosts, T0, T), 1008 mail_ids(T). 1009 1010mail_id(Id-List, NewPosts) :- 1011 mail_handlers(List, Boxes, Content), 1012 ( Boxes = [accept(MH:Handler, In)] 1013 -> extend_args(Handler, Content, Goal), 1014 phrase(MH:Goal, In), 1015 mailboxes(In, NewBoxes), 1016 keysort(NewBoxes, Keyed), 1017 group_pairs_by_key(Keyed, NewPosts) 1018 ; Boxes = [ignore(_, _)|_] 1019 -> NewPosts = [] 1020 ; Boxes = [accept(_,_),accept(_,_)|_] 1021 -> print_message(error, html(multiple_receivers(Id))), 1022 NewPosts = [] 1023 ; print_message(error, html(no_receiver(Id))), 1024 NewPosts = [] 1025 ). 1026 1027add_new_posts([], T, T). 1028add_new_posts([Id-Posts|NewT], T0, T) :- 1029 ( select(Id-List0, T0, Id-List, T1) 1030 -> append(List0, Posts, List) 1031 ; debug(html(mailman), 'Stuck with new posts on ~q', [Id]), 1032 fail 1033 ), 1034 add_new_posts(NewT, T1, T).
post(Module,HTML)
into Posters and the remainder in
Handlers. Handlers consists of accept(Handler, Tokens)
and
ignore(_,_)
.1043mail_handlers([], [], []). 1044mail_handlers([post(Module,HTML)|T0], H, [Module:HTML|T]) :- 1045 !, 1046 mail_handlers(T0, H, T). 1047mail_handlers([H|T0], [H|T], C) :- 1048 mail_handlers(T0, T, C). 1049 1050extend_args(Term, Extra, NewTerm) :- 1051 Term =.. [Name|Args], 1052 append(Args, [Extra], NewArgs), 1053 NewTerm =.. [Name|NewArgs].
1064sorted_html(List) -->
1065 { sort(List, Unique) },
1066 html(Unique).
html_receive(head)
. Unlike sorted_html//1, it calls
a user hook html_head_expansion/2 to process the
collected head material into a term suitable for html//1.
1079head_html(List) --> 1080 { list_to_set(List, Unique), 1081 html_expand_head(Unique, NewList) 1082 }, 1083 html(NewList). 1084 1085:- multifile 1086 html_head_expansion/2. 1087 1088html_expand_head(List0, List) :- 1089 html_head_expansion(List0, List1), 1090 List0 \== List1, 1091 !, 1092 html_expand_head(List1, List). 1093html_expand_head(List, List). 1094 1095 1096 /******************************* 1097 * LAYOUT * 1098 *******************************/ 1099 1100pre_open(Env) --> 1101 { layout(Env, N-_, _) 1102 }, 1103 !, 1104 [ nl(N) ]. 1105pre_open(_) --> []. 1106 1107post_open(Env) --> 1108 { layout(Env, _-N, _) 1109 }, 1110 !, 1111 [ nl(N) ]. 1112post_open(_) --> 1113 []. 1114 1115pre_close(head) --> 1116 !, 1117 html_receive(head, head_html), 1118 { layout(head, _, N-_) }, 1119 [ nl(N) ]. 1120pre_close(Env) --> 1121 { layout(Env, _, N-_) 1122 }, 1123 !, 1124 [ nl(N) ]. 1125pre_close(_) --> 1126 []. 1127 1128post_close(Env) --> 1129 { layout(Env, _, _-N) 1130 }, 1131 !, 1132 [ nl(N) ]. 1133post_close(_) --> 1134 [].
1151:- multifile 1152 layout/3. 1153 1154layout(table, 2-1, 1-2). 1155layout(blockquote, 2-1, 1-2). 1156layout(pre, 2-1, 0-2). 1157layout(textarea, 1-1, 0-1). 1158layout(center, 2-1, 1-2). 1159layout(dl, 2-1, 1-2). 1160layout(ul, 1-1, 1-1). 1161layout(ol, 2-1, 1-2). 1162layout(form, 2-1, 1-2). 1163layout(frameset, 2-1, 1-2). 1164layout(address, 2-1, 1-2). 1165 1166layout(head, 1-1, 1-1). 1167layout(body, 1-1, 1-1). 1168layout(script, 1-1, 1-1). 1169layout(style, 1-1, 1-1). 1170layout(select, 1-1, 1-1). 1171layout(map, 1-1, 1-1). 1172layout(html, 1-1, 1-1). 1173layout(caption, 1-1, 1-1). 1174layout(applet, 1-1, 1-1). 1175 1176layout(tr, 1-0, 0-1). 1177layout(option, 1-0, 0-1). 1178layout(li, 1-0, 0-1). 1179layout(dt, 1-0, -). 1180layout(dd, 0-0, -). 1181layout(title, 1-0, 0-1). 1182 1183layout(h1, 2-0, 0-2). 1184layout(h2, 2-0, 0-2). 1185layout(h3, 2-0, 0-2). 1186layout(h4, 2-0, 0-2). 1187 1188layout(iframe, 1-1, 1-1). 1189 1190layout(hr, 1-1, empty). % empty elements 1191layout(br, 0-1, empty). 1192layout(img, 0-0, empty). 1193layout(meta, 1-1, empty). 1194layout(base, 1-1, empty). 1195layout(link, 1-1, empty). 1196layout(input, 0-0, empty). 1197layout(frame, 1-1, empty). 1198layout(col, 0-0, empty). 1199layout(area, 1-0, empty). 1200layout(input, 1-0, empty). 1201layout(param, 1-0, empty). 1202 1203layout(p, 2-1, -). % omited close 1204layout(td, 0-0, 0-0). 1205 1206layout(div, 1-0, 0-1). 1207 1208 /******************************* 1209 * PRINTING * 1210 *******************************/
1225print_html(List) :- 1226 current_output(Out), 1227 mailman(List), 1228 write_html(List, Out). 1229print_html(Out, List) :- 1230 ( html_current_option(dialect(xhtml)) 1231 -> stream_property(Out, encoding(Enc)), 1232 ( Enc == utf8 1233 -> true 1234 ; print_message(warning, html(wrong_encoding(Out, Enc))) 1235 ), 1236 xml_header(Hdr), 1237 write(Out, Hdr), nl(Out) 1238 ; true 1239 ), 1240 mailman(List), 1241 write_html(List, Out), 1242 flush_output(Out). 1243 1244write_html([], _). 1245write_html([nl(N)|T], Out) :- 1246 !, 1247 join_nl(T, N, Lines, T2), 1248 write_nl(Lines, Out), 1249 write_html(T2, Out). 1250write_html([mailbox(_, Box)|T], Out) :- 1251 !, 1252 ( Box = accept(_, Accepted), 1253 nonvar(Accepted) 1254 -> write_html(Accepted, Out) 1255 ; true 1256 ), 1257 write_html(T, Out). 1258write_html([cdata(Env, Tokens)|T], Out) :- 1259 !, 1260 with_output_to(string(CDATA), write_html(Tokens, current_output)), 1261 valid_cdata(Env, CDATA), 1262 write(Out, CDATA), 1263 write_html(T, Out). 1264write_html([H|T], Out) :- 1265 write(Out, H), 1266 write_html(T, Out). 1267 1268join_nl([nl(N0)|T0], N1, N, T) :- 1269 !, 1270 N2 is max(N0, N1), 1271 join_nl(T0, N2, N, T). 1272join_nl(L, N, N, L). 1273 1274write_nl(0, _) :- !. 1275write_nl(N, Out) :- 1276 nl(Out), 1277 N1 is N - 1, 1278 write_nl(N1, Out).
<script>
. This implies it cannot contain </script/
.
There is no escape for this and the script generator must use a
work-around using features of the script language. For example,
when using JavaScript, "</script>" can be written as
"<\/script>".
1292valid_cdata(Env, String) :- 1293 atomics_to_string(['</', Env, '>'], End), 1294 sub_atom_icasechk(String, _, End), 1295 !, 1296 domain_error(cdata, String). 1297valid_cdata(_, _).
phrase(html(DOM), Tokens), html_print_length(Tokens, Len), format('Content-type: text/html; charset=UTF-8~n'), format('Content-length: ~d~n~n', [Len]), print_html(Tokens)
1313html_print_length(List, Len) :- 1314 mailman(List), 1315 ( html_current_option(dialect(xhtml)) 1316 -> xml_header(Hdr), 1317 atom_length(Hdr, L0), 1318 L1 is L0+1 % one for newline 1319 ; L1 = 0 1320 ), 1321 html_print_length(List, L1, Len). 1322 1323html_print_length([], L, L). 1324html_print_length([nl(N)|T], L0, L) :- 1325 !, 1326 join_nl(T, N, Lines, T1), 1327 L1 is L0 + Lines, % assume only \n! 1328 html_print_length(T1, L1, L). 1329html_print_length([mailbox(_, Box)|T], L0, L) :- 1330 !, 1331 ( Box = accept(_, Accepted) 1332 -> html_print_length(Accepted, L0, L1) 1333 ; L1 = L0 1334 ), 1335 html_print_length(T, L1, L). 1336html_print_length([cdata(_, CDATA)|T], L0, L) :- 1337 !, 1338 html_print_length(CDATA, L0, L1), 1339 html_print_length(T, L1, L). 1340html_print_length([H|T], L0, L) :- 1341 atom_length(H, Hlen), 1342 L1 is L0+Hlen, 1343 html_print_length(T, L1, L).
http_wrapper.pl
for a
page constructed from Head and Body. The HTTP Content-type
is provided by html_current_option/1.1353reply_html_page(Head, Body) :- 1354 reply_html_page(default, Head, Body). 1355reply_html_page(Style, Head, Body) :- 1356 html_current_option(content_type(Type)), 1357 phrase(page(Style, Head, Body), HTML), 1358 forall(html_header_hook(Style), true), 1359 format('Content-type: ~w~n~n', [Type]), 1360 print_html(HTML).
Content-type
header is emitted. It allows for emitting additional headers
depending on the first argument of reply_html_page/3.1371 /******************************* 1372 * META-PREDICATE SUPPORT * 1373 *******************************/
html
. For example:
:- html_meta page(html,html,?,?).
1389html_meta(Spec) :- 1390 throw(error(context_error(nodirective, html_meta(Spec)), _)). 1391 1392html_meta_decls(Var, _, _) :- 1393 var(Var), 1394 !, 1395 instantiation_error(Var). 1396html_meta_decls((A,B), (MA,MB), [MH|T]) :- 1397 !, 1398 html_meta_decl(A, MA, MH), 1399 html_meta_decls(B, MB, T). 1400html_meta_decls(A, MA, [MH]) :- 1401 html_meta_decl(A, MA, MH). 1402 1403html_meta_decl(Head, MetaHead, 1404 html_write:html_meta_head(GenHead, Module, Head)) :- 1405 functor(Head, Name, Arity), 1406 functor(GenHead, Name, Arity), 1407 prolog_load_context(module, Module), 1408 Head =.. [Name|HArgs], 1409 maplist(html_meta_decl, HArgs, MArgs), 1410 MetaHead =.. [Name|MArgs]. 1411 1412html_meta_decl(html, :) :- !. 1413html_meta_decl(Meta, Meta). 1414 1415systemterm_expansion((:- html_meta(Heads)), 1416 [ (:- meta_predicate(Meta)) 1417 | MetaHeads 1418 ]) :- 1419 html_meta_decls(Heads, Meta, MetaHeads). 1420 1421:- multifile 1422 html_meta_head/3. 1423 1424html_meta_colours(Head, Goal, built_in-Colours) :- 1425 Head =.. [_|MArgs], 1426 Goal =.. [_|Args], 1427 maplist(meta_colours, MArgs, Args, Colours). 1428 1429meta_colours(html, HTML, Colours) :- 1430 !, 1431 html_colours(HTML, Colours). 1432meta_colours(I, _, Colours) :- 1433 integer(I), I>=0, 1434 !, 1435 Colours = meta(I). 1436meta_colours(_, _, classify). 1437 1438html_meta_called(Head, Goal, Called) :- 1439 Head =.. [_|MArgs], 1440 Goal =.. [_|Args], 1441 meta_called(MArgs, Args, Called, []). 1442 1443meta_called([], [], Called, Called). 1444meta_called([html|MT], [A|AT], Called, Tail) :- 1445 !, 1446 phrase(called_by(A), Called, Tail1), 1447 meta_called(MT, AT, Tail1, Tail). 1448meta_called([0|MT], [A|AT], [A|CT0], CT) :- 1449 !, 1450 meta_called(MT, AT, CT0, CT). 1451meta_called([I|MT], [A|AT], [A+I|CT0], CT) :- 1452 integer(I), I>0, 1453 !, 1454 meta_called(MT, AT, CT0, CT). 1455meta_called([_|MT], [_|AT], Called, Tail) :- 1456 !, 1457 meta_called(MT, AT, Called, Tail). 1458 1459 1460:- html_meta 1461 html( , , ), 1462 page( , , ), 1463 page( , , , ), 1464 page( , , , , ), 1465 pagehead( , , , ), 1466 pagebody( , , , ), 1467 reply_html_page( , ), 1468 reply_html_page( , , ), 1469 html_post( , , , ). 1470 1471 1472 /******************************* 1473 * PCE EMACS SUPPORT * 1474 *******************************/ 1475 1476:- multifile 1477 prolog_colour:goal_colours/2, 1478 prolog_colour:style/2, 1479 prolog_colour:message//1, 1480 prolog:called_by/2. 1481 1482prolog_colourgoal_colours(Goal, Colours) :- 1483 html_meta_head(Goal, _Module, Head), 1484 html_meta_colours(Head, Goal, Colours). 1485prolog_colourgoal_colours(html_meta(_), 1486 built_in-[meta_declarations([html])]). 1487 1488 % TBD: Check with do_expand! 1489html_colours(Var, classify) :- 1490 var(Var), 1491 !. 1492html_colours(\List, html_raw-[list-Colours]) :- 1493 is_list(List), 1494 !, 1495 list_colours(List, Colours). 1496html_colours(\_, html_call-[dcg]) :- !. 1497html_colours(_:Term, built_in-[classify,Colours]) :- 1498 !, 1499 html_colours(Term, Colours). 1500html_colours(&(Entity), functor-[entity(Entity)]) :- !. 1501html_colours(List, list-ListColours) :- 1502 List = [_|_], 1503 !, 1504 list_colours(List, ListColours). 1505html_colours(Format-Args, functor-[FormatColor,ArgsColors]) :- 1506 !, 1507 format_colours(Format, FormatColor), 1508 format_arg_colours(Args, Format, ArgsColors). 1509html_colours(Term, TermColours) :- 1510 compound(Term), 1511 compound_name_arguments(Term, Name, Args), 1512 Name \== '.', 1513 !, 1514 ( Args = [One] 1515 -> TermColours = html(Name)-ArgColours, 1516 ( layout(Name, _, empty) 1517 -> attr_colours(One, ArgColours) 1518 ; html_colours(One, Colours), 1519 ArgColours = [Colours] 1520 ) 1521 ; Args = [AList,Content] 1522 -> TermColours = html(Name)-[AColours, Colours], 1523 attr_colours(AList, AColours), 1524 html_colours(Content, Colours) 1525 ; TermColours = error 1526 ). 1527html_colours(_, classify). 1528 1529list_colours(Var, classify) :- 1530 var(Var), 1531 !. 1532list_colours([], []). 1533list_colours([H0|T0], [H|T]) :- 1534 !, 1535 html_colours(H0, H), 1536 list_colours(T0, T). 1537list_colours(Last, Colours) :- % improper list 1538 html_colours(Last, Colours). 1539 1540attr_colours(Var, classify) :- 1541 var(Var), 1542 !. 1543attr_colours([], classify) :- !. 1544attr_colours(Term, list-Elements) :- 1545 Term = [_|_], 1546 !, 1547 attr_list_colours(Term, Elements). 1548attr_colours(Name=Value, built_in-[html_attribute(Name), VColour]) :- 1549 !, 1550 attr_value_colour(Value, VColour). 1551attr_colours(NS:Term, built_in-[ html_xmlns(NS), 1552 html_attribute(Name)-[classify] 1553 ]) :- 1554 compound(Term), 1555 compound_name_arity(Term, Name, 1). 1556attr_colours(Term, html_attribute(Name)-[VColour]) :- 1557 compound(Term), 1558 compound_name_arity(Term, Name, 1), 1559 !, 1560 Term =.. [Name,Value], 1561 attr_value_colour(Value, VColour). 1562attr_colours(Name, html_attribute(Name)) :- 1563 atom(Name), 1564 !. 1565attr_colours(Term, classify) :- 1566 compound(Term), 1567 compound_name_arity(Term, '.', 2), 1568 !. 1569attr_colours(_, error). 1570 1571attr_list_colours(Var, classify) :- 1572 var(Var), 1573 !. 1574attr_list_colours([], []). 1575attr_list_colours([H0|T0], [H|T]) :- 1576 attr_colours(H0, H), 1577 attr_list_colours(T0, T). 1578 1579attr_value_colour(Var, classify) :- 1580 var(Var). 1581attr_value_colour(location_by_id(ID), sgml_attr_function-[Colour]) :- 1582 !, 1583 location_id(ID, Colour). 1584attr_value_colour(#(ID), sgml_attr_function-[Colour]) :- 1585 !, 1586 location_id(ID, Colour). 1587attr_value_colour(A+B, sgml_attr_function-[CA,CB]) :- 1588 !, 1589 attr_value_colour(A, CA), 1590 attr_value_colour(B, CB). 1591attr_value_colour(encode(_), sgml_attr_function-[classify]) :- !. 1592attr_value_colour(Atom, classify) :- 1593 atomic(Atom), 1594 !. 1595attr_value_colour([_|_], classify) :- !. 1596attr_value_colour(_Fmt-_Args, classify) :- !. 1597attr_value_colour(Term, classify) :- 1598 compound(Term), 1599 compound_name_arity(Term, '.', 2), 1600 !. 1601attr_value_colour(_, error). 1602 1603location_id(ID, classify) :- 1604 var(ID), 1605 !. 1606:- if(current_predicate(http_location_for_id/1)). 1607location_id(ID, Class) :- 1608 ( catch(http_location_by_id(ID, Location), _, fail) 1609 -> Class = http_location_for_id(Location) 1610 ; Class = http_no_location_for_id(ID) 1611 ). 1612:- endif. 1613location_id(_, classify). 1614 1615format_colours(Format, format_string) :- atom(Format), !. 1616format_colours(Format, format_string) :- string(Format), !. 1617format_colours(_Format, type_error(text)). 1618 1619format_arg_colours(Args, _Format, classify) :- is_list(Args), !. 1620format_arg_colours(_, _, type_error(list)). 1621 1622:- op(990, xfx, :=). % allow compiling without XPCE 1623:- op(200, fy, @). 1624 1625prolog_colourstyle(html(_), [colour(magenta4), bold(true)]). 1626prolog_colourstyle(entity(_), [colour(magenta4)]). 1627prolog_colourstyle(html_attribute(_), [colour(magenta4)]). 1628prolog_colourstyle(html_xmlns(_), [colour(magenta4)]). 1629prolog_colourstyle(format_string(_), [colour(magenta4)]). 1630prolog_colourstyle(sgml_attr_function, [colour(blue)]). 1631prolog_colourstyle(http_location_for_id(_), [bold(true)]). 1632prolog_colourstyle(http_no_location_for_id(_), [colour(red), bold(true)]). 1633 1634 1635prolog_colourmessage(html(Element)) --> 1636 [ '~w: SGML element'-[Element] ]. 1637prolog_colourmessage(entity(Entity)) --> 1638 [ '~w: SGML entity'-[Entity] ]. 1639prolog_colourmessage(html_attribute(Attr)) --> 1640 [ '~w: SGML attribute'-[Attr] ]. 1641prolog_colourmessage(sgml_attr_function) --> 1642 [ 'SGML Attribute function'-[] ]. 1643prolog_colourmessage(http_location_for_id(Location)) --> 1644 [ 'ID resolves to ~w'-[Location] ]. 1645prolog_colourmessage(http_no_location_for_id(ID)) --> 1646 [ '~w: no such ID'-[ID] ]. 1647 1648 1649% prolog:called_by(+Goal, -Called) 1650% 1651% Hook into library(pce_prolog_xref). Called is a list of callable 1652% or callable+N to indicate (DCG) arglist extension. 1653 1654 1655prologcalled_by(Goal, Called) :- 1656 html_meta_head(Goal, _Module, Head), 1657 html_meta_called(Head, Goal, Called). 1658 1659called_by(Term) --> 1660 called_by(Term, _). 1661 1662called_by(Var, _) --> 1663 { var(Var) }, 1664 !, 1665 []. 1666called_by(\G, M) --> 1667 !, 1668 ( { is_list(G) } 1669 -> called_by(G, M) 1670 ; {atom(M)} 1671 -> [(M:G)+2] 1672 ; [G+2] 1673 ). 1674called_by([], _) --> 1675 !, 1676 []. 1677called_by([H|T], M) --> 1678 !, 1679 called_by(H, M), 1680 called_by(T, M). 1681called_by(M:Term, _) --> 1682 !, 1683 ( {atom(M)} 1684 -> called_by(Term, M) 1685 ; [] 1686 ). 1687called_by(Term, M) --> 1688 { compound(Term), 1689 !, 1690 Term =.. [_|Args] 1691 }, 1692 called_by(Args, M). 1693called_by(_, _) --> 1694 []. 1695 1696:- multifile 1697 prolog:hook/1. 1698 1699prologhook(body(_,_,_)). 1700prologhook(body(_,_,_,_)). 1701prologhook(head(_,_,_)). 1702prologhook(head(_,_,_,_)). 1703 1704 1705 /******************************* 1706 * MESSAGES * 1707 *******************************/ 1708 1709:- multifile 1710 prolog:message/3. 1711 1712prologmessage(html(expand_failed(What))) --> 1713 [ 'Failed to translate to HTML: ~p'-[What] ]. 1714prologmessage(html(wrong_encoding(Stream, Enc))) --> 1715 [ 'XHTML demands UTF-8 encoding; encoding of ~p is ~w'-[Stream, Enc] ]. 1716prologmessage(html(multiple_receivers(Id))) --> 1717 [ 'html_post//2: multiple receivers for: ~p'-[Id] ]. 1718prologmessage(html(no_receiver(Id))) --> 1719 [ 'html_post//2: no receivers for: ~p'-[Id] ]
Write HTML text
Most code doesn't need to use this directly; instead use library(http/http_server), which combines this library with the typical HTTP libraries that most servers need.
The purpose of this library is to simplify writing HTML pages. Of course, it is possible to use format/3 to write to the HTML stream directly, but this is generally not very satisfactory:
This module tries to remedy these problems. The idea is to translate a Prolog term into an HTML document. We use DCG for most of the generation.
International documents
The library supports the generation of international documents, but this is currently limited to using UTF-8 encoded HTML or XHTML documents. It is strongly recommended to use the following mime-type.
When generating XHTML documents, the output stream must be in UTF-8 encoding. */