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-2025, University of Amsterdam 7 VU University Amsterdam 8 SWI-Prolog Solutions b.v. 9 All rights reserved. 10 11 Redistribution and use in source and binary forms, with or without 12 modification, are permitted provided that the following conditions 13 are met: 14 15 1. Redistributions of source code must retain the above copyright 16 notice, this list of conditions and the following disclaimer. 17 18 2. Redistributions in binary form must reproduce the above copyright 19 notice, this list of conditions and the following disclaimer in 20 the documentation and/or other materials provided with the 21 distribution. 22 23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 29 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 33 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 POSSIBILITY OF SUCH DAMAGE. 35*/ 36 37:- module(html_write, 38 [ reply_html_page/2, % :Head, :Body 39 reply_html_page/3, % +Style, :Head, :Body 40 reply_html_partial/1, % +HTML 41 42 % Basic output routines 43 page//1, % :Content 44 page//2, % :Head, :Body 45 page//3, % +Style, :Head, :Body 46 html//1, % :Content 47 48 % Option processing 49 html_set_options/1, % +OptionList 50 html_current_option/1, % ?Option 51 52 % repositioning HTML elements 53 html_post//2, % +Id, :Content 54 html_receive//1, % +Id 55 html_receive//2, % +Id, :Handler 56 xhtml_ns//2, % +Id, +Value 57 html_root_attribute//2, % +Name, +Value 58 59 html/4, % {|html||quasi quotations|} 60 61 % Useful primitives for expanding 62 html_begin//1, % +EnvName[(Attribute...)] 63 html_end//1, % +EnvName 64 html_quoted//1, % +Text 65 html_quoted_attribute//1, % +Attribute 66 67 % Emitting the HTML code 68 print_html/1, % +List 69 print_html/2, % +Stream, +List 70 html_print_length/2, % +List, -Length 71 72 % Extension support 73 (html_meta)/1, % +Spec 74 op(1150, fx, html_meta) 75 ]). 76:- use_module(html_quasiquotations, [html/4]). 77:- use_module(library(debug),[debug/3]). 78:- use_module(html_decl, [(html_meta)/1, html_no_content/1, op(_,_,_)]). 79:- autoload(library(error), 80 [must_be/2,domain_error/2,instantiation_error/1]). 81:- autoload(library(lists), 82 [permutation/2,selectchk/3,append/3,select/4,list_to_set/2]). 83:- autoload(library(option),[option/2]). 84:- autoload(library(pairs),[group_pairs_by_key/2]). 85:- autoload(library(sgml),[xml_quote_cdata/3,xml_quote_attribute/3]). 86:- autoload(library(uri),[uri_encoded/3]). 87:- autoload(library(url),[www_form_encode/2]). 88 89% Quote output 90:- set_prolog_flag(generate_debug_info, false). 91 92:- meta_predicate 93 reply_html_page( , , ), 94 reply_html_page( , ), 95 html( , , ), 96 page( , , ), 97 page( , , , ), 98 pagehead( , , , ), 99 pagebody( , , , ), 100 html_receive( , , , ), 101 html_post( , , , ). 102 103:- multifile 104 expand//1, % +HTMLElement 105 expand_attribute_value//1, % +HTMLAttributeValue 106 html_header_hook/1. % +Style
143 /******************************* 144 * SETTINGS * 145 *******************************/
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.
171html_set_options(Options) :- 172 must_be(list, Options), 173 set_options(Options). 174 175set_options([]). 176set_options([H|T]) :- 177 html_set_option(H), 178 set_options(T). 179 180html_set_option(dialect(Dialect0)) :- 181 !, 182 must_be(oneof([html,html4,xhtml,html5]), Dialect0), 183 ( html_version_alias(Dialect0, Dialect) 184 -> true 185 ; Dialect = Dialect0 186 ), 187 set_prolog_flag(html_dialect, Dialect). 188html_set_option(doctype(Atom)) :- 189 !, 190 must_be(atom, Atom), 191 current_prolog_flag(html_dialect, Dialect), 192 dialect_doctype_flag(Dialect, Flag), 193 set_prolog_flag(Flag, Atom). 194html_set_option(content_type(Atom)) :- 195 !, 196 must_be(atom, Atom), 197 current_prolog_flag(html_dialect, Dialect), 198 dialect_content_type_flag(Dialect, Flag), 199 set_prolog_flag(Flag, Atom). 200html_set_option(O) :- 201 domain_error(html_option, O). 202 203html_version_alias(html, html4).
209html_current_option(dialect(Dialect)) :- 210 current_prolog_flag(html_dialect, Dialect). 211html_current_option(doctype(DocType)) :- 212 current_prolog_flag(html_dialect, Dialect), 213 dialect_doctype_flag(Dialect, Flag), 214 current_prolog_flag(Flag, DocType). 215html_current_option(content_type(ContentType)) :- 216 current_prolog_flag(html_dialect, Dialect), 217 dialect_content_type_flag(Dialect, Flag), 218 current_prolog_flag(Flag, ContentType). 219 220dialect_doctype_flag(html4, html4_doctype). 221dialect_doctype_flag(html5, html5_doctype). 222dialect_doctype_flag(xhtml, xhtml_doctype). 223 224dialect_content_type_flag(html4, html4_content_type). 225dialect_content_type_flag(html5, html5_content_type). 226dialect_content_type_flag(xhtml, xhtml_content_type). 227 228option_default(html_dialect, html5). 229option_default(html4_doctype, 230 'HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" \c 231 "http://www.w3.org/TR/html4/loose.dtd"'). 232option_default(html5_doctype, 233 'html'). 234option_default(xhtml_doctype, 235 'html PUBLIC "-//W3C//DTD XHTML 1.0 \c 236 Transitional//EN" \c 237 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"'). 238option_default(html4_content_type, 'text/html; charset=UTF-8'). 239option_default(html5_content_type, 'text/html; charset=UTF-8'). 240option_default(xhtml_content_type, 'application/xhtml+xml; charset=UTF-8').
246init_options :- 247 ( option_default(Name, Value), 248 ( current_prolog_flag(Name, _) 249 -> true 250 ; create_prolog_flag(Name, Value, []) 251 ), 252 fail 253 ; true 254 ). 255 256:- init_options.
262xml_header('<?xml version=\'1.0\' encoding=\'UTF-8\'?>').
268ns(xhtml, 'http://www.w3.org/1999/xhtml'). 269 270 271 /******************************* 272 * PAGE * 273 *******************************/
<!DOCTYPE>
header. The
actual doctype is read from the option doctype
as defined by
html_set_options/1.282page(Content) --> 283 doctype, 284 html(html(Content)). 285 286page(Head, Body) --> 287 page(default, Head, Body). 288 289page(Style, Head, Body) --> 290 doctype, 291 content_type, 292 html_begin(html), 293 pagehead(Style, Head), 294 pagebody(Style, Body), 295 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 ...304doctype --> 305 { html_current_option(doctype(DocType)), 306 DocType \== '' 307 }, 308 !, 309 [ '<!DOCTYPE ', DocType, '>' ]. 310doctype --> 311 []. 312 313content_type --> 314 { html_current_option(content_type(Type)) 315 }, 316 !, 317 html_post(head, meta([ 'http-equiv'('content-type'), 318 content(Type) 319 ], [])). 320content_type --> 321 { html_current_option(dialect(html5)) }, 322 !, 323 html_post(head, meta('charset=UTF-8')). 324content_type --> 325 []. 326 327pagehead(_, Head) --> 328 { functor(Head, head, _) 329 }, 330 !, 331 html(Head). 332pagehead(Style, Head) --> 333 { strip_module(Head, M, _), 334 hook_module(M, HM, head//2) 335 }, 336 HM:head(Style, Head), 337 !. 338pagehead(_, Head) --> 339 { strip_module(Head, M, _), 340 hook_module(M, HM, head//1) 341 }, 342 HM:head(Head), 343 !. 344pagehead(_, Head) --> 345 html(head(Head)). 346 347 348pagebody(_, Body) --> 349 { functor(Body, body, _) 350 }, 351 !, 352 html(Body). 353pagebody(Style, Body) --> 354 { strip_module(Body, M, _), 355 hook_module(M, HM, body//2) 356 }, 357 HM:body(Style, Body), 358 !. 359pagebody(_, Body) --> 360 { strip_module(Body, M, _), 361 hook_module(M, HM, body//1) 362 }, 363 HM:body(Body), 364 !. 365pagebody(_, Body) --> 366 html(body(Body)). 367 368 369hook_module(M, M, PI) :- 370 current_predicate(M:PI), 371 !. 372hook_module(_, user, PI) :- 373 current_predicate(user:PI).
380html(Spec) --> 381 { strip_module(Spec, M, T) }, 382 qhtml(T, M). 383 384qhtml(Var, _) --> 385 { var(Var), 386 !, 387 instantiation_error(Var) 388 }. 389qhtml([], _) --> 390 !, 391 []. 392qhtml([H|T], M) --> 393 !, 394 html_expand(H, M), 395 qhtml(T, M). 396qhtml(X, M) --> 397 html_expand(X, M). 398 399html_expand(Var, _) --> 400 { var(Var), 401 !, 402 instantiation_error(Var) 403 }. 404html_expand(Term, Module) --> 405 do_expand(Term, Module), 406 !. 407html_expand(Term, _Module) --> 408 { print_message(error, html(expand_failed(Term))) }. 409 410 411do_expand(Token, _) --> % call user hooks 412 expand(Token), 413 !. 414do_expand(Fmt-Args, _) --> 415 !, 416 { format(string(String), Fmt, Args) 417 }, 418 html_quoted(String). 419do_expand(\List, Module) --> 420 { is_list(List) 421 }, 422 !, 423 raw(List, Module). 424do_expand(\Term, Module, In, Rest) :- 425 !, 426 call(Module:Term, In, Rest). 427do_expand(Module:Term, _) --> 428 !, 429 qhtml(Term, Module). 430do_expand(&(Entity), _) --> 431 !, 432 { integer(Entity) 433 -> format(string(String), '&#~d;', [Entity]) 434 ; format(string(String), '&~w;', [Entity]) 435 }, 436 [ String ]. 437do_expand(Token, _) --> 438 { atomic(Token) 439 }, 440 !, 441 html_quoted(Token). 442do_expand(element(Env, Attributes, Contents), M) --> 443 !, 444 ( { Contents == [], 445 html_current_option(dialect(xhtml)) 446 } 447 -> xhtml_empty(Env, Attributes) 448 ; html_begin(Env, Attributes), 449 qhtml(Env, Contents, M), 450 html_end(Env) 451 ). 452do_expand(Term, M) --> 453 { Term =.. [Env, Contents] 454 }, 455 !, 456 ( { html_no_content(Env) } 457 -> html_begin(Env, Contents) 458 ; ( { Contents == [], 459 html_current_option(dialect(xhtml)) 460 } 461 -> xhtml_empty(Env, []) 462 ; html_begin(Env), 463 qhtml(Env, Contents, M), 464 html_end(Env) 465 ) 466 ). 467do_expand(Term, M) --> 468 { Term =.. [Env, Attributes, Contents], 469 check_non_empty(Contents, Env, Term) 470 }, 471 !, 472 ( { Contents == [], 473 html_current_option(dialect(xhtml)) 474 } 475 -> xhtml_empty(Env, Attributes) 476 ; html_begin(Env, Attributes), 477 qhtml(Env, Contents, M), 478 html_end(Env) 479 ). 480 481qhtml(Env, Contents, M) --> 482 { cdata_element(Env), 483 phrase(cdata(Contents, M), Tokens) 484 }, 485 !, 486 [ cdata(Env, Tokens) ]. 487qhtml(_, Contents, M) --> 488 qhtml(Contents, M). 489 490 491check_non_empty([], _, _) :- !. 492check_non_empty(_, Tag, Term) :- 493 html_no_content(Tag), 494 !, 495 print_message(warning, 496 format('Using empty element with content: ~p', [Term])). 497check_non_empty(_, _, _). 498 499cdata(List, M) --> 500 { is_list(List) }, 501 !, 502 raw(List, M). 503cdata(One, M) --> 504 raw_element(One, M).
510raw([], _) --> 511 []. 512raw([H|T], Module) --> 513 raw_element(H, Module), 514 raw(T, Module). 515 516raw_element(Var, _) --> 517 { var(Var), 518 !, 519 instantiation_error(Var) 520 }. 521raw_element(\List, Module) --> 522 { is_list(List) 523 }, 524 !, 525 raw(List, Module). 526raw_element(\Term, Module, In, Rest) :- 527 !, 528 call(Module:Term, In, Rest). 529raw_element(Module:Term, _) --> 530 !, 531 raw_element(Term, Module). 532raw_element(Fmt-Args, _) --> 533 !, 534 { format(string(S), Fmt, Args) }, 535 [S]. 536raw_element(Value, _) --> 537 { must_be(atomic, Value) }, 538 [Value].
html(table(border=1, \table_content))
html_begin(table(border=1) table_content, html_end(table)
559html_begin(Env) --> 560 { Env =.. [Name|Attributes] 561 }, 562 html_begin(Name, Attributes). 563 564html_begin(Env, Attributes) --> 565 pre_open(Env), 566 [<], 567 [Env], 568 attributes(Env, Attributes), 569 ( { html_no_content(Env), 570 html_current_option(dialect(xhtml)) 571 } 572 -> ['/>'] 573 ; [>] 574 ), 575 post_open(Env). 576 577html_end(Env) --> % empty element or omited close 578 { html_no_content(Env) 579 ; layout(Env, _, -), 580 html_current_option(dialect(html)) 581 }, 582 !, 583 []. 584html_end(Env) --> 585 pre_close(Env), 586 ['</'], 587 [Env], 588 ['>'], 589 post_close(Env).
595xhtml_empty(Env, Attributes) -->
596 pre_open(Env),
597 [<],
598 [Env],
599 attributes(Attributes),
600 ['/>'].
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.
625xhtml_ns(Id, Value) --> 626 { html_current_option(dialect(xhtml)) }, 627 !, 628 html_post(xmlns, \attribute(xmlns:Id=Value)). 629xhtml_ns(_, _) --> 630 [].
html(div(...)), html_root_attribute(lang, en), ...
643html_root_attribute(Name, Value) -->
644 html_post(html_begin, \attribute(Name=Value)).
651attributes(html, L) --> 652 !, 653 ( { html_current_option(dialect(xhtml)) } 654 -> ( { option(xmlns(_), L) } 655 -> attributes(L) 656 ; { ns(xhtml, NS) }, 657 attributes([xmlns(NS)|L]) 658 ), 659 html_receive(xmlns) 660 ; attributes(L), 661 html_noreceive(xmlns) 662 ), 663 html_receive(html_begin). 664attributes(_, L) --> 665 attributes(L). 666 667attributes([]) --> 668 !, 669 []. 670attributes([H|T]) --> 671 !, 672 attribute(H), 673 attributes(T). 674attributes(One) --> 675 attribute(One). 676 677attribute(Name=Value) --> 678 !, 679 [' '], name(Name), [ '="' ], 680 attribute_value(Value), 681 ['"']. 682attribute(NS:Term) --> 683 !, 684 { Term =.. [Name, Value] 685 }, 686 !, 687 attribute((NS:Name)=Value). 688attribute(Term) --> 689 { Term =.. [Name, Value] 690 }, 691 !, 692 attribute(Name=Value). 693attribute(Atom) --> % Value-abbreviated attribute 694 { atom(Atom) 695 }, 696 [ ' ', Atom ]. 697 698name(NS:Name) --> 699 !, 700 [NS, :, Name]. 701name(Name) --> 702 [ 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.
724attribute_value(List) --> 725 { is_list(List) }, 726 !, 727 attribute_value_m(List). 728attribute_value(Value) --> 729 attribute_value_s(Value). 730 731% emit a single attribute value 732 733attribute_value_s(Var) --> 734 { var(Var), 735 !, 736 instantiation_error(Var) 737 }. 738attribute_value_s(A+B) --> 739 !, 740 attribute_value(A), 741 ( { is_list(B) } 742 -> ( { B == [] } 743 -> [] 744 ; [?], search_parameters(B) 745 ) 746 ; attribute_value(B) 747 ). 748attribute_value_s(encode(Value)) --> 749 !, 750 { uri_encoded(query_value, Value, Encoded) }, 751 [ Encoded ]. 752attribute_value_s(Value) --> 753 expand_attribute_value(Value), 754 !. 755attribute_value_s(Fmt-Args) --> 756 !, 757 { format(string(Value), Fmt, Args) }, 758 html_quoted_attribute(Value). 759attribute_value_s(Value) --> 760 html_quoted_attribute(Value). 761 762search_parameters([H|T]) --> 763 search_parameter(H), 764 ( {T == []} 765 -> [] 766 ; ['&'], 767 search_parameters(T) 768 ). 769 770search_parameter(Var) --> 771 { var(Var), 772 !, 773 instantiation_error(Var) 774 }. 775search_parameter(Name=Value) --> 776 { www_form_encode(Value, Encoded) }, 777 [Name, =, Encoded]. 778search_parameter(Term) --> 779 { Term =.. [Name, Value], 780 !, 781 www_form_encode(Value, Encoded) 782 }, 783 [Name, =, Encoded]. 784search_parameter(Term) --> 785 { domain_error(search_parameter, Term) 786 }.
body(class([c1, c2]), Body)
Emits <body class="c1 c2"> ...
798attribute_value_m([]) --> 799 []. 800attribute_value_m([H|T]) --> 801 attribute_value_s(H), 802 ( { T == [] } 803 -> [] 804 ; [' '], 805 attribute_value_m(T) 806 ). 807 808 809 /******************************* 810 * QUOTING RULES * 811 *******************************/
html(b(Text))
826html_quoted(Text) -->
827 { xml_quote_cdata(Text, Quoted, utf8) },
828 [ Quoted ].
839html_quoted_attribute(Text) -->
840 { xml_quote_attribute(Text, Quoted, utf8) },
841 [ Quoted ].
</
needs to be escaped.848cdata_element(script). 849cdata_element(style). 850 851 852 /******************************* 853 * REPOSITIONING HTML * 854 *******************************/
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) ], ...)
886html_post(Id, Content) -->
887 { strip_module(Content, M, C) },
888 [ mailbox(Id, post(M, C)) ].
901html_receive(Id) -->
902 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.
921html_receive(Id, Handler) -->
922 { strip_module(Handler, M, P) },
923 [ mailbox(Id, accept(M:P, _)) ].
929html_noreceive(Id) -->
930 [ mailbox(Id, ignore(_,_)) ].
head
and script
boxes at
the end.941mailman(Tokens) :- 942 ( html_token(mailbox(_, accept(_, Accepted)), Tokens) 943 -> true 944 ), 945 var(Accepted), % not yet executed 946 !, 947 mailboxes(Tokens, Boxes), 948 keysort(Boxes, Keyed), 949 group_pairs_by_key(Keyed, PerKey), 950 move_last(PerKey, script, PerKey1), 951 move_last(PerKey1, head, PerKey2), 952 ( permutation(PerKey2, PerKeyPerm), 953 ( mail_ids(PerKeyPerm) 954 -> ! 955 ; debug(html(mailman), 956 'Failed mail delivery order; retrying', []), 957 fail 958 ) 959 -> true 960 ; print_message(error, html(cyclic_mailboxes)) 961 ). 962mailman(_). 963 964move_last(Box0, Id, Box) :- 965 selectchk(Id-List, Box0, Box1), 966 !, 967 append(Box1, [Id-List], Box). 968move_last(Box, _, Box).
cdata(Elem, Tokens)
.975html_token(Token, [H|T]) :- 976 html_token_(T, H, Token). 977 978html_token_(_, Token, Token) :- !. 979html_token_(_, cdata(_,Tokens), Token) :- 980 html_token(Token, Tokens). 981html_token_([H|T], _, Token) :- 982 html_token_(T, H, Token).
988mailboxes(Tokens, MailBoxes) :- 989 mailboxes(Tokens, MailBoxes, []). 990 991mailboxes([], List, List). 992mailboxes([mailbox(Id, Value)|T0], [Id-Value|T], Tail) :- 993 !, 994 mailboxes(T0, T, Tail). 995mailboxes([cdata(_Type, Tokens)|T0], Boxes, Tail) :- 996 !, 997 mailboxes(Tokens, Boxes, Tail0), 998 mailboxes(T0, Tail0, Tail). 999mailboxes([_|T0], T, Tail) :- 1000 mailboxes(T0, T, Tail). 1001 1002mail_ids([]). 1003mail_ids([H|T0]) :- 1004 mail_id(H, NewPosts), 1005 add_new_posts(NewPosts, T0, T), 1006 mail_ids(T). 1007 1008mail_id(Id-List, NewPosts) :- 1009 mail_handlers(List, Boxes, Content), 1010 ( Boxes = [accept(MH:Handler, In)] 1011 -> extend_args(Handler, Content, Goal), 1012 phrase(MH:Goal, In), 1013 mailboxes(In, NewBoxes), 1014 keysort(NewBoxes, Keyed), 1015 group_pairs_by_key(Keyed, NewPosts) 1016 ; Boxes = [ignore(_, _)|_] 1017 -> NewPosts = [] 1018 ; Boxes = [accept(_,_),accept(_,_)|_] 1019 -> print_message(error, html(multiple_receivers(Id))), 1020 NewPosts = [] 1021 ; print_message(error, html(no_receiver(Id))), 1022 NewPosts = [] 1023 ). 1024 1025add_new_posts([], T, T). 1026add_new_posts([Id-Posts|NewT], T0, T) :- 1027 ( select(Id-List0, T0, Id-List, T1) 1028 -> append(List0, Posts, List) 1029 ; debug(html(mailman), 'Stuck with new posts on ~q', [Id]), 1030 fail 1031 ), 1032 add_new_posts(NewT, T1, T).
post(Module,HTML)
into Posters and the remainder in
Handlers. Handlers consists of accept(Handler, Tokens)
and
ignore(_,_)
.1041mail_handlers([], [], []). 1042mail_handlers([post(Module,HTML)|T0], H, [Module:HTML|T]) :- 1043 !, 1044 mail_handlers(T0, H, T). 1045mail_handlers([H|T0], [H|T], C) :- 1046 mail_handlers(T0, T, C). 1047 1048extend_args(Term, Extra, NewTerm) :- 1049 Term =.. [Name|Args], 1050 append(Args, [Extra], NewArgs), 1051 NewTerm =.. [Name|NewArgs].
1062sorted_html(List) -->
1063 { sort(List, Unique) },
1064 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.
1077head_html(List) --> 1078 { list_to_set(List, Unique), 1079 html_expand_head(Unique, NewList) 1080 }, 1081 html(NewList). 1082 1083:- multifile 1084 html_head_expansion/2. 1085 1086html_expand_head(List0, List) :- 1087 html_head_expansion(List0, List1), 1088 List0 \== List1, 1089 !, 1090 html_expand_head(List1, List). 1091html_expand_head(List, List). 1092 1093 1094 /******************************* 1095 * LAYOUT * 1096 *******************************/ 1097 1098pre_open(Env) --> 1099 { layout(Env, N-_, _) 1100 }, 1101 !, 1102 [ nl(N) ]. 1103pre_open(_) --> []. 1104 1105post_open(Env) --> 1106 { layout(Env, _-N, _) 1107 }, 1108 !, 1109 [ nl(N) ]. 1110post_open(_) --> 1111 []. 1112 1113pre_close(head) --> 1114 !, 1115 html_receive(head, head_html), 1116 { layout(head, _, N-_) }, 1117 [ nl(N) ]. 1118pre_close(Env) --> 1119 { layout(Env, _, N-_) 1120 }, 1121 !, 1122 [ nl(N) ]. 1123pre_close(_) --> 1124 []. 1125 1126post_close(Env) --> 1127 { layout(Env, _, _-N) 1128 }, 1129 !, 1130 [ nl(N) ]. 1131post_close(_) --> 1132 [].
1148:- multifile 1149 layout/3. 1150 1151layout(table, 2-1, 1-2). 1152layout(blockquote, 2-1, 1-2). 1153layout(pre, 2-1, 0-2). 1154layout(textarea, 1-1, 0-1). 1155layout(center, 2-1, 1-2). 1156layout(dl, 2-1, 1-2). 1157layout(ul, 1-1, 1-1). 1158layout(ol, 2-1, 1-2). 1159layout(form, 2-1, 1-2). 1160layout(frameset, 2-1, 1-2). 1161layout(address, 2-1, 1-2). 1162 1163layout(head, 1-1, 1-1). 1164layout(body, 1-1, 1-1). 1165layout(script, 1-1, 1-1). 1166layout(style, 1-1, 1-1). 1167layout(select, 1-1, 1-1). 1168layout(map, 1-1, 1-1). 1169layout(html, 1-1, 1-1). 1170layout(caption, 1-1, 1-1). 1171layout(applet, 1-1, 1-1). 1172 1173layout(tr, 1-0, 0-1). 1174layout(option, 1-0, 0-1). 1175layout(li, 1-0, 0-1). 1176layout(dt, 1-0, -). 1177layout(dd, 0-0, -). 1178layout(title, 1-0, 0-1). 1179 1180layout(h1, 2-0, 0-2). 1181layout(h2, 2-0, 0-2). 1182layout(h3, 2-0, 0-2). 1183layout(h4, 2-0, 0-2). 1184 1185layout(iframe, 1-1, 1-1). 1186 1187layout(area, 1-0, -). 1188layout(base, 1-1, -). 1189layout(br, 0-1, -). 1190layout(col, 0-0, -). 1191layout(embed, 1-1, -). 1192layout(hr, 1-1, -). 1193layout(img, 0-0, -). 1194layout(input, 1-0, -). 1195layout(link, 1-1, -). 1196layout(meta, 1-1, -). 1197layout(param, 1-0, -). 1198layout(source, 1-0, -). 1199layout(track, 1-0, -). 1200layout(wbr, 0-0, -). 1201 1202layout(p, 2-1, -). % omited close 1203layout(td, 0-0, 0-0). 1204 1205layout(div, 1-0, 0-1). 1206 1207 /******************************* 1208 * PRINTING * 1209 *******************************/
1224print_html(List) :- 1225 current_output(Out), 1226 mailman(List), 1227 write_html(List, Out). 1228print_html(Out, List) :- 1229 ( html_current_option(dialect(xhtml)) 1230 -> stream_property(Out, encoding(Enc)), 1231 ( Enc == utf8 1232 -> true 1233 ; print_message(warning, html(wrong_encoding(Out, Enc))) 1234 ), 1235 xml_header(Hdr), 1236 write(Out, Hdr), nl(Out) 1237 ; true 1238 ), 1239 mailman(List), 1240 write_html(List, Out), 1241 flush_output(Out). 1242 1243write_html([], _). 1244write_html([nl(N)|T], Out) :- 1245 !, 1246 join_nl(T, N, Lines, T2), 1247 write_nl(Lines, Out), 1248 write_html(T2, Out). 1249write_html([mailbox(_, Box)|T], Out) :- 1250 !, 1251 ( Box = accept(_, Accepted), 1252 nonvar(Accepted) 1253 -> write_html(Accepted, Out) 1254 ; true 1255 ), 1256 write_html(T, Out). 1257write_html([cdata(Env, Tokens)|T], Out) :- 1258 !, 1259 with_output_to(string(CDATA), write_html(Tokens, current_output)), 1260 valid_cdata(Env, CDATA), 1261 write(Out, CDATA), 1262 write_html(T, Out). 1263write_html([H|T], Out) :- 1264 write(Out, H), 1265 write_html(T, Out). 1266 1267join_nl([nl(N0)|T0], N1, N, T) :- 1268 !, 1269 N2 is max(N0, N1), 1270 join_nl(T0, N2, N, T). 1271join_nl(L, N, N, L). 1272 1273write_nl(0, _) :- !. 1274write_nl(N, Out) :- 1275 nl(Out), 1276 N1 is N - 1, 1277 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>".
1291valid_cdata(Env, String) :- 1292 atomics_to_string(['</', Env, '>'], End), 1293 sub_atom_icasechk(String, _, End), 1294 !, 1295 domain_error(cdata, String). 1296valid_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)
1312html_print_length(List, Len) :- 1313 mailman(List), 1314 ( html_current_option(dialect(xhtml)) 1315 -> xml_header(Hdr), 1316 atom_length(Hdr, L0), 1317 L1 is L0+1 % one for newline 1318 ; L1 = 0 1319 ), 1320 html_print_length(List, L1, Len). 1321 1322html_print_length([], L, L). 1323html_print_length([nl(N)|T], L0, L) :- 1324 !, 1325 join_nl(T, N, Lines, T1), 1326 L1 is L0 + Lines, % assume only \n! 1327 html_print_length(T1, L1, L). 1328html_print_length([mailbox(_, Box)|T], L0, L) :- 1329 !, 1330 ( Box = accept(_, Accepted) 1331 -> html_print_length(Accepted, L0, L1) 1332 ; L1 = L0 1333 ), 1334 html_print_length(T, L1, L). 1335html_print_length([cdata(_, CDATA)|T], L0, L) :- 1336 !, 1337 html_print_length(CDATA, L0, L1), 1338 html_print_length(T, L1, L). 1339html_print_length([H|T], L0, L) :- 1340 atom_length(H, Hlen), 1341 L1 is L0+Hlen, 1342 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.
1355reply_html_page(Head, Body) :- 1356 reply_html_page(default, Head, Body). 1357reply_html_page(Style, Head, Body) :- 1358 html_current_option(content_type(Type)), 1359 phrase(page(Style, Head, Body), HTML), 1360 forall(html_header_hook(Style), true), 1361 format('Content-type: ~w~n~n', [Type]), 1362 print_html(HTML).
DOCTYPE
header, <html>
, <head>
or <body>
. It is intended for
JavaScript handlers that request a partial document and insert that
somewhere into the existing page DOM.
1376reply_html_partial(HTML) :-
1377 html_current_option(content_type(Type)),
1378 phrase(html(HTML), Tokens),
1379 format('Content-type: ~w~n~n', [Type]),
1380 print_html(Tokens).
Content-type
header is emitted. It allows for emitting additional headers
depending on the first argument of reply_html_page/3.1390:- html_meta 1391 html( , , ), 1392 page( , , ), 1393 page( , , , ), 1394 page( , , , , ), 1395 pagehead( , , , ), 1396 pagebody( , , , ), 1397 reply_html_page( , ), 1398 reply_html_page( , , ), 1399 html_post( , , , ). 1400 1401 1402:- multifile 1403 prolog:hook/1. 1404 1405prologhook(body(_,_,_)). 1406prologhook(body(_,_,_,_)). 1407prologhook(head(_,_,_)). 1408prologhook(head(_,_,_,_)). 1409 1410 1411 /******************************* 1412 * MESSAGES * 1413 *******************************/ 1414 1415:- multifile 1416 prolog:message/3. 1417 1418prologmessage(html(expand_failed(What))) --> 1419 [ 'Failed to translate to HTML: ~p'-[What] ]. 1420prologmessage(html(wrong_encoding(Stream, Enc))) --> 1421 [ 'XHTML demands UTF-8 encoding; encoding of ~p is ~w'-[Stream, Enc] ]. 1422prologmessage(html(multiple_receivers(Id))) --> 1423 [ 'html_post//2: multiple receivers for: ~p'-[Id] ]. 1424prologmessage(html(no_receiver(Id))) --> 1425 [ '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. */