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-2015, 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(library(error)). 75:- use_module(library(apply)). 76:- use_module(library(lists)). 77:- use_module(library(option)). 78:- use_module(library(pairs)). 79:- use_module(library(sgml)). % Quote output 80:- use_module(library(uri)). 81:- use_module(library(debug)). 82:- use_module(html_quasiquotations). 83:- use_module(library(http/http_dispatch), [http_location_by_id/2]). 84 85:- set_prolog_flag(generate_debug_info, false). 86 87:- meta_predicate 88 reply_html_page( , , ), 89 reply_html_page( , ), 90 html( , , ), 91 page( , , ), 92 page( , , , ), 93 pagehead( , , , ), 94 pagebody( , , , ), 95 html_receive( , , , ), 96 html_post( , , , ). 97 98:- multifile 99 expand//1, % +HTMLElement 100 expand_attribute_value//1. % +HTMLAttributeValue
133 /******************************* 134 * SETTINGS * 135 *******************************/
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.
161html_set_options(Options) :- 162 must_be(list, Options), 163 set_options(Options). 164 165set_options([]). 166set_options([H|T]) :- 167 html_set_option(H), 168 set_options(T). 169 170html_set_option(dialect(Dialect0)) :- 171 !, 172 must_be(oneof([html,html4,xhtml,html5]), Dialect0), 173 ( html_version_alias(Dialect0, Dialect) 174 -> true 175 ; Dialect = Dialect0 176 ), 177 set_prolog_flag(html_dialect, Dialect). 178html_set_option(doctype(Atom)) :- 179 !, 180 must_be(atom, Atom), 181 current_prolog_flag(html_dialect, Dialect), 182 dialect_doctype_flag(Dialect, Flag), 183 set_prolog_flag(Flag, Atom). 184html_set_option(content_type(Atom)) :- 185 !, 186 must_be(atom, Atom), 187 current_prolog_flag(html_dialect, Dialect), 188 dialect_content_type_flag(Dialect, Flag), 189 set_prolog_flag(Flag, Atom). 190html_set_option(O) :- 191 domain_error(html_option, O). 192 193html_version_alias(html, html4).
199html_current_option(dialect(Dialect)) :- 200 current_prolog_flag(html_dialect, Dialect). 201html_current_option(doctype(DocType)) :- 202 current_prolog_flag(html_dialect, Dialect), 203 dialect_doctype_flag(Dialect, Flag), 204 current_prolog_flag(Flag, DocType). 205html_current_option(content_type(ContentType)) :- 206 current_prolog_flag(html_dialect, Dialect), 207 dialect_content_type_flag(Dialect, Flag), 208 current_prolog_flag(Flag, ContentType). 209 210dialect_doctype_flag(html4, html4_doctype). 211dialect_doctype_flag(html5, html5_doctype). 212dialect_doctype_flag(xhtml, xhtml_doctype). 213 214dialect_content_type_flag(html4, html4_content_type). 215dialect_content_type_flag(html5, html5_content_type). 216dialect_content_type_flag(xhtml, xhtml_content_type). 217 218option_default(html_dialect, html5). 219option_default(html4_doctype, 220 'HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" \c 221 "http://www.w3.org/TR/html4/loose.dtd"'). 222option_default(html5_doctype, 223 'html'). 224option_default(xhtml_doctype, 225 'html PUBLIC "-//W3C//DTD XHTML 1.0 \c 226 Transitional//EN" \c 227 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"'). 228option_default(html4_content_type, 'text/html; charset=UTF-8'). 229option_default(html5_content_type, 'text/html; charset=UTF-8'). 230option_default(xhtml_content_type, 'application/xhtml+xml; charset=UTF-8').
236init_options :- 237 ( option_default(Name, Value), 238 ( current_prolog_flag(Name, _) 239 -> true 240 ; create_prolog_flag(Name, Value, []) 241 ), 242 fail 243 ; true 244 ). 245 246:- init_options.
252xml_header('<?xml version=\'1.0\' encoding=\'UTF-8\'?>').
258ns(xhtml, 'http://www.w3.org/1999/xhtml'). 259 260 261 /******************************* 262 * PAGE * 263 *******************************/
<!DOCTYPE>
header. The
actual doctype is read from the option doctype
as defined by
html_set_options/1.272page(Content) --> 273 doctype, 274 html(html(Content)). 275 276page(Head, Body) --> 277 page(default, Head, Body). 278 279page(Style, Head, Body) --> 280 doctype, 281 content_type, 282 html_begin(html), 283 pagehead(Style, Head), 284 pagebody(Style, Body), 285 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 ...294doctype --> 295 { html_current_option(doctype(DocType)), 296 DocType \== '' 297 }, 298 !, 299 [ '<!DOCTYPE ', DocType, '>' ]. 300doctype --> 301 []. 302 303content_type --> 304 { html_current_option(content_type(Type)) 305 }, 306 !, 307 html_post(head, meta([ 'http-equiv'('content-type'), 308 content(Type) 309 ], [])). 310content_type --> 311 { html_current_option(dialect(html5)) }, 312 !, 313 html_post(head, meta('charset=UTF-8')). 314content_type --> 315 []. 316 317pagehead(_, Head) --> 318 { functor(Head, head, _) 319 }, 320 !, 321 html(Head). 322pagehead(Style, Head) --> 323 { strip_module(Head, M, _), 324 hook_module(M, HM, head//2) 325 }, 326 HM:head(Style, Head), 327 !. 328pagehead(_, Head) --> 329 { strip_module(Head, M, _), 330 hook_module(M, HM, head//1) 331 }, 332 HM:head(Head), 333 !. 334pagehead(_, Head) --> 335 html(head(Head)). 336 337 338pagebody(_, Body) --> 339 { functor(Body, body, _) 340 }, 341 !, 342 html(Body). 343pagebody(Style, Body) --> 344 { strip_module(Body, M, _), 345 hook_module(M, HM, body//2) 346 }, 347 HM:body(Style, Body), 348 !. 349pagebody(_, Body) --> 350 { strip_module(Body, M, _), 351 hook_module(M, HM, body//1) 352 }, 353 HM:body(Body), 354 !. 355pagebody(_, Body) --> 356 html(body(Body)). 357 358 359hook_module(M, M, PI) :- 360 current_predicate(M:PI), 361 !. 362hook_module(_, user, PI) :- 363 current_predicate(user:PI).
370html(Spec) --> 371 { strip_module(Spec, M, T) }, 372 qhtml(T, M). 373 374qhtml(Var, _) --> 375 { var(Var), 376 !, 377 instantiation_error(Var) 378 }. 379qhtml([], _) --> 380 !, 381 []. 382qhtml([H|T], M) --> 383 !, 384 html_expand(H, M), 385 qhtml(T, M). 386qhtml(X, M) --> 387 html_expand(X, M). 388 389html_expand(Var, _) --> 390 { var(Var), 391 !, 392 instantiation_error(Var) 393 }. 394html_expand(Term, Module) --> 395 do_expand(Term, Module), 396 !. 397html_expand(Term, _Module) --> 398 { print_message(error, html(expand_failed(Term))) }. 399 400 401do_expand(Token, _) --> % call user hooks 402 expand(Token), 403 !. 404do_expand(Fmt-Args, _) --> 405 !, 406 { format(string(String), Fmt, Args) 407 }, 408 html_quoted(String). 409do_expand(\List, Module) --> 410 { is_list(List) 411 }, 412 !, 413 raw(List, Module). 414do_expand(\Term, Module, In, Rest) :- 415 !, 416 call(Module:Term, In, Rest). 417do_expand(Module:Term, _) --> 418 !, 419 qhtml(Term, Module). 420do_expand(&(Entity), _) --> 421 !, 422 { integer(Entity) 423 -> format(string(String), '&#~d;', [Entity]) 424 ; format(string(String), '&~w;', [Entity]) 425 }, 426 [ String ]. 427do_expand(Token, _) --> 428 { atomic(Token) 429 }, 430 !, 431 html_quoted(Token). 432do_expand(element(Env, Attributes, Contents), M) --> 433 !, 434 ( { Contents == [], 435 html_current_option(dialect(xhtml)) 436 } 437 -> xhtml_empty(Env, Attributes) 438 ; html_begin(Env, Attributes), 439 qhtml(Env, Contents, M), 440 html_end(Env) 441 ). 442do_expand(Term, M) --> 443 { Term =.. [Env, Contents] 444 }, 445 !, 446 ( { layout(Env, _, empty) 447 } 448 -> html_begin(Env, Contents) 449 ; ( { Contents == [], 450 html_current_option(dialect(xhtml)) 451 } 452 -> xhtml_empty(Env, []) 453 ; html_begin(Env), 454 qhtml(Env, Contents, M), 455 html_end(Env) 456 ) 457 ). 458do_expand(Term, M) --> 459 { Term =.. [Env, Attributes, Contents], 460 check_non_empty(Contents, Env, Term) 461 }, 462 !, 463 ( { Contents == [], 464 html_current_option(dialect(xhtml)) 465 } 466 -> xhtml_empty(Env, Attributes) 467 ; html_begin(Env, Attributes), 468 qhtml(Env, Contents, M), 469 html_end(Env) 470 ). 471 472qhtml(Env, Contents, M) --> 473 { cdata_element(Env), 474 phrase(cdata(Contents, M), Tokens) 475 }, 476 !, 477 [ cdata(Env, Tokens) ]. 478qhtml(_, Contents, M) --> 479 qhtml(Contents, M). 480 481 482check_non_empty([], _, _) :- !. 483check_non_empty(_, Tag, Term) :- 484 layout(Tag, _, empty), 485 !, 486 print_message(warning, 487 format('Using empty element with content: ~p', [Term])). 488check_non_empty(_, _, _). 489 490cdata(List, M) --> 491 { is_list(List) }, 492 !, 493 raw(List, M). 494cdata(One, M) --> 495 raw_element(One, M).
501raw([], _) --> 502 []. 503raw([H|T], Module) --> 504 raw_element(H, Module), 505 raw(T, Module). 506 507raw_element(Var, _) --> 508 { var(Var), 509 !, 510 instantiation_error(Var) 511 }. 512raw_element(\List, Module) --> 513 { is_list(List) 514 }, 515 !, 516 raw(List, Module). 517raw_element(\Term, Module, In, Rest) :- 518 !, 519 call(Module:Term, In, Rest). 520raw_element(Module:Term, _) --> 521 !, 522 raw_element(Term, Module). 523raw_element(Fmt-Args, _) --> 524 !, 525 { format(string(S), Fmt, Args) }, 526 [S]. 527raw_element(Value, _) --> 528 { must_be(atomic, Value) }, 529 [Value].
html(table(border=1, \table_content))
html_begin(table(border=1) table_content, html_end(table)
550html_begin(Env) --> 551 { Env =.. [Name|Attributes] 552 }, 553 html_begin(Name, Attributes). 554 555html_begin(Env, Attributes) --> 556 pre_open(Env), 557 [<], 558 [Env], 559 attributes(Env, Attributes), 560 ( { layout(Env, _, empty), 561 html_current_option(dialect(xhtml)) 562 } 563 -> ['/>'] 564 ; [>] 565 ), 566 post_open(Env). 567 568html_end(Env) --> % empty element or omited close 569 { layout(Env, _, -), 570 html_current_option(dialect(html)) 571 ; layout(Env, _, empty) 572 }, 573 !, 574 []. 575html_end(Env) --> 576 pre_close(Env), 577 ['</'], 578 [Env], 579 ['>'], 580 post_close(Env).
586xhtml_empty(Env, Attributes) -->
587 pre_open(Env),
588 [<],
589 [Env],
590 attributes(Attributes),
591 ['/>'].
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.
616xhtml_ns(Id, Value) --> 617 { html_current_option(dialect(xhtml)) }, 618 !, 619 html_post(xmlns, \attribute(xmlns:Id=Value)). 620xhtml_ns(_, _) --> 621 [].
html(div(...)), html_root_attribute(lang, en), ...
634html_root_attribute(Name, Value) -->
635 html_post(html_begin, \attribute(Name=Value)).
642attributes(html, L) --> 643 !, 644 ( { html_current_option(dialect(xhtml)) } 645 -> ( { option(xmlns(_), L) } 646 -> attributes(L) 647 ; { ns(xhtml, NS) }, 648 attributes([xmlns(NS)|L]) 649 ), 650 html_receive(xmlns) 651 ; attributes(L), 652 html_noreceive(xmlns) 653 ), 654 html_receive(html_begin). 655attributes(_, L) --> 656 attributes(L). 657 658attributes([]) --> 659 !, 660 []. 661attributes([H|T]) --> 662 !, 663 attribute(H), 664 attributes(T). 665attributes(One) --> 666 attribute(One). 667 668attribute(Name=Value) --> 669 !, 670 [' '], name(Name), [ '="' ], 671 attribute_value(Value), 672 ['"']. 673attribute(NS:Term) --> 674 !, 675 { Term =.. [Name, Value] 676 }, 677 !, 678 attribute((NS:Name)=Value). 679attribute(Term) --> 680 { Term =.. [Name, Value] 681 }, 682 !, 683 attribute(Name=Value). 684attribute(Atom) --> % Value-abbreviated attribute 685 { atom(Atom) 686 }, 687 [ ' ', Atom ]. 688 689name(NS:Name) --> 690 !, 691 [NS, :, Name]. 692name(Name) --> 693 [ 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.
715:- multifile 716 expand_attribute_value//1. 717 718attribute_value(List) --> 719 { is_list(List) }, 720 !, 721 attribute_value_m(List). 722attribute_value(Value) --> 723 attribute_value_s(Value). 724 725% emit a single attribute value 726 727attribute_value_s(Var) --> 728 { var(Var), 729 !, 730 instantiation_error(Var) 731 }. 732attribute_value_s(A+B) --> 733 !, 734 attribute_value(A), 735 ( { is_list(B) } 736 -> ( { B == [] } 737 -> [] 738 ; [?], search_parameters(B) 739 ) 740 ; attribute_value(B) 741 ). 742attribute_value_s(encode(Value)) --> 743 !, 744 { uri_encoded(query_value, Value, Encoded) }, 745 [ Encoded ]. 746attribute_value_s(Value) --> 747 expand_attribute_value(Value), 748 !. 749attribute_value_s(Fmt-Args) --> 750 !, 751 { format(string(Value), Fmt, Args) }, 752 html_quoted_attribute(Value). 753attribute_value_s(Value) --> 754 html_quoted_attribute(Value). 755 756search_parameters([H|T]) --> 757 search_parameter(H), 758 ( {T == []} 759 -> [] 760 ; ['&'], 761 search_parameters(T) 762 ). 763 764search_parameter(Var) --> 765 { var(Var), 766 !, 767 instantiation_error(Var) 768 }. 769search_parameter(Name=Value) --> 770 { www_form_encode(Value, Encoded) }, 771 [Name, =, Encoded]. 772search_parameter(Term) --> 773 { Term =.. [Name, Value], 774 !, 775 www_form_encode(Value, Encoded) 776 }, 777 [Name, =, Encoded]. 778search_parameter(Term) --> 779 { domain_error(search_parameter, Term) 780 }.
body(class([c1, c2]), Body)
Emits <body class="c1 c2"> ...
792attribute_value_m([]) --> 793 []. 794attribute_value_m([H|T]) --> 795 attribute_value_s(H), 796 ( { T == [] } 797 -> [] 798 ; [' '], 799 attribute_value_m(T) 800 ). 801 802 803 /******************************* 804 * QUOTING RULES * 805 *******************************/
html(b(Text))
820html_quoted(Text) -->
821 { xml_quote_cdata(Text, Quoted, utf8) },
822 [ Quoted ].
833html_quoted_attribute(Text) -->
834 { xml_quote_attribute(Text, Quoted, utf8) },
835 [ Quoted ].
</
needs to be escaped.842cdata_element(script). 843cdata_element(style). 844 845 846 /******************************* 847 * REPOSITIONING HTML * 848 *******************************/
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) ], ...)
880html_post(Id, Content) -->
881 { strip_module(Content, M, C) },
882 [ mailbox(Id, post(M, C)) ].
895html_receive(Id) -->
896 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.
915html_receive(Id, Handler) -->
916 { strip_module(Handler, M, P) },
917 [ mailbox(Id, accept(M:P, _)) ].
923html_noreceive(Id) -->
924 [ mailbox(Id, ignore(_,_)) ].
head
and script
boxes at
the end.935mailman(Tokens) :- 936 ( html_token(mailbox(_, accept(_, Accepted)), Tokens) 937 -> true 938 ), 939 var(Accepted), % not yet executed 940 !, 941 mailboxes(Tokens, Boxes), 942 keysort(Boxes, Keyed), 943 group_pairs_by_key(Keyed, PerKey), 944 move_last(PerKey, script, PerKey1), 945 move_last(PerKey1, head, PerKey2), 946 ( permutation(PerKey2, PerKeyPerm), 947 ( mail_ids(PerKeyPerm) 948 -> ! 949 ; debug(html(mailman), 950 'Failed mail delivery order; retrying', []), 951 fail 952 ) 953 -> true 954 ; print_message(error, html(cyclic_mailboxes)) 955 ). 956mailman(_). 957 958move_last(Box0, Id, Box) :- 959 selectchk(Id-List, Box0, Box1), 960 !, 961 append(Box1, [Id-List], Box). 962move_last(Box, _, Box).
cdata(Elem, Tokens)
.969html_token(Token, [H|T]) :- 970 html_token_(T, H, Token). 971 972html_token_(_, Token, Token) :- !. 973html_token_(_, cdata(_,Tokens), Token) :- 974 html_token(Token, Tokens). 975html_token_([H|T], _, Token) :- 976 html_token_(T, H, Token).
982mailboxes(Tokens, MailBoxes) :- 983 mailboxes(Tokens, MailBoxes, []). 984 985mailboxes([], List, List). 986mailboxes([mailbox(Id, Value)|T0], [Id-Value|T], Tail) :- 987 !, 988 mailboxes(T0, T, Tail). 989mailboxes([cdata(_Type, Tokens)|T0], Boxes, Tail) :- 990 !, 991 mailboxes(Tokens, Boxes, Tail0), 992 mailboxes(T0, Tail0, Tail). 993mailboxes([_|T0], T, Tail) :- 994 mailboxes(T0, T, Tail). 995 996mail_ids([]). 997mail_ids([H|T0]) :- 998 mail_id(H, NewPosts), 999 add_new_posts(NewPosts, T0, T), 1000 mail_ids(T). 1001 1002mail_id(Id-List, NewPosts) :- 1003 mail_handlers(List, Boxes, Content), 1004 ( Boxes = [accept(MH:Handler, In)] 1005 -> extend_args(Handler, Content, Goal), 1006 phrase(MH:Goal, In), 1007 mailboxes(In, NewBoxes), 1008 keysort(NewBoxes, Keyed), 1009 group_pairs_by_key(Keyed, NewPosts) 1010 ; Boxes = [ignore(_, _)|_] 1011 -> NewPosts = [] 1012 ; Boxes = [accept(_,_),accept(_,_)|_] 1013 -> print_message(error, html(multiple_receivers(Id))), 1014 NewPosts = [] 1015 ; print_message(error, html(no_receiver(Id))), 1016 NewPosts = [] 1017 ). 1018 1019add_new_posts([], T, T). 1020add_new_posts([Id-Posts|NewT], T0, T) :- 1021 ( select(Id-List0, T0, Id-List, T1) 1022 -> append(List0, Posts, List) 1023 ; debug(html(mailman), 'Stuck with new posts on ~q', [Id]), 1024 fail 1025 ), 1026 add_new_posts(NewT, T1, T).
post(Module,HTML)
into Posters and the remainder in
Handlers. Handlers consists of accept(Handler, Tokens)
and
ignore(_,_)
.1035mail_handlers([], [], []). 1036mail_handlers([post(Module,HTML)|T0], H, [Module:HTML|T]) :- 1037 !, 1038 mail_handlers(T0, H, T). 1039mail_handlers([H|T0], [H|T], C) :- 1040 mail_handlers(T0, T, C). 1041 1042extend_args(Term, Extra, NewTerm) :- 1043 Term =.. [Name|Args], 1044 append(Args, [Extra], NewArgs), 1045 NewTerm =.. [Name|NewArgs].
1056sorted_html(List) -->
1057 { sort(List, Unique) },
1058 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.
1071head_html(List) --> 1072 { list_to_set(List, Unique), 1073 html_expand_head(Unique, NewList) 1074 }, 1075 html(NewList). 1076 1077:- multifile 1078 html_head_expansion/2. 1079 1080html_expand_head(List0, List) :- 1081 html_head_expansion(List0, List1), 1082 List0 \== List1, 1083 !, 1084 html_expand_head(List1, List). 1085html_expand_head(List, List). 1086 1087 1088 /******************************* 1089 * LAYOUT * 1090 *******************************/ 1091 1092pre_open(Env) --> 1093 { layout(Env, N-_, _) 1094 }, 1095 !, 1096 [ nl(N) ]. 1097pre_open(_) --> []. 1098 1099post_open(Env) --> 1100 { layout(Env, _-N, _) 1101 }, 1102 !, 1103 [ nl(N) ]. 1104post_open(_) --> 1105 []. 1106 1107pre_close(head) --> 1108 !, 1109 html_receive(head, head_html), 1110 { layout(head, _, N-_) }, 1111 [ nl(N) ]. 1112pre_close(Env) --> 1113 { layout(Env, _, N-_) 1114 }, 1115 !, 1116 [ nl(N) ]. 1117pre_close(_) --> 1118 []. 1119 1120post_close(Env) --> 1121 { layout(Env, _, _-N) 1122 }, 1123 !, 1124 [ nl(N) ]. 1125post_close(_) --> 1126 [].
1143:- multifile 1144 layout/3. 1145 1146layout(table, 2-1, 1-2). 1147layout(blockquote, 2-1, 1-2). 1148layout(pre, 2-1, 0-2). 1149layout(textarea, 1-1, 0-1). 1150layout(center, 2-1, 1-2). 1151layout(dl, 2-1, 1-2). 1152layout(ul, 1-1, 1-1). 1153layout(ol, 2-1, 1-2). 1154layout(form, 2-1, 1-2). 1155layout(frameset, 2-1, 1-2). 1156layout(address, 2-1, 1-2). 1157 1158layout(head, 1-1, 1-1). 1159layout(body, 1-1, 1-1). 1160layout(script, 1-1, 1-1). 1161layout(style, 1-1, 1-1). 1162layout(select, 1-1, 1-1). 1163layout(map, 1-1, 1-1). 1164layout(html, 1-1, 1-1). 1165layout(caption, 1-1, 1-1). 1166layout(applet, 1-1, 1-1). 1167 1168layout(tr, 1-0, 0-1). 1169layout(option, 1-0, 0-1). 1170layout(li, 1-0, 0-1). 1171layout(dt, 1-0, -). 1172layout(dd, 0-0, -). 1173layout(title, 1-0, 0-1). 1174 1175layout(h1, 2-0, 0-2). 1176layout(h2, 2-0, 0-2). 1177layout(h3, 2-0, 0-2). 1178layout(h4, 2-0, 0-2). 1179 1180layout(iframe, 1-1, 1-1). 1181 1182layout(hr, 1-1, empty). % empty elements 1183layout(br, 0-1, empty). 1184layout(img, 0-0, empty). 1185layout(meta, 1-1, empty). 1186layout(base, 1-1, empty). 1187layout(link, 1-1, empty). 1188layout(input, 0-0, empty). 1189layout(frame, 1-1, empty). 1190layout(col, 0-0, empty). 1191layout(area, 1-0, empty). 1192layout(input, 1-0, empty). 1193layout(param, 1-0, empty). 1194 1195layout(p, 2-1, -). % omited close 1196layout(td, 0-0, 0-0). 1197 1198layout(div, 1-0, 0-1). 1199 1200 /******************************* 1201 * PRINTING * 1202 *******************************/
1217print_html(List) :- 1218 current_output(Out), 1219 mailman(List), 1220 write_html(List, Out). 1221print_html(Out, List) :- 1222 ( html_current_option(dialect(xhtml)) 1223 -> stream_property(Out, encoding(Enc)), 1224 ( Enc == utf8 1225 -> true 1226 ; print_message(warning, html(wrong_encoding(Out, Enc))) 1227 ), 1228 xml_header(Hdr), 1229 write(Out, Hdr), nl(Out) 1230 ; true 1231 ), 1232 mailman(List), 1233 write_html(List, Out), 1234 flush_output(Out). 1235 1236write_html([], _). 1237write_html([nl(N)|T], Out) :- 1238 !, 1239 join_nl(T, N, Lines, T2), 1240 write_nl(Lines, Out), 1241 write_html(T2, Out). 1242write_html([mailbox(_, Box)|T], Out) :- 1243 !, 1244 ( Box = accept(_, Accepted) 1245 -> write_html(Accepted, Out) 1246 ; true 1247 ), 1248 write_html(T, Out). 1249write_html([cdata(Env, Tokens)|T], Out) :- 1250 !, 1251 with_output_to(string(CDATA), write_html(Tokens, current_output)), 1252 valid_cdata(Env, CDATA), 1253 write(Out, CDATA), 1254 write_html(T, Out). 1255write_html([H|T], Out) :- 1256 write(Out, H), 1257 write_html(T, Out). 1258 1259join_nl([nl(N0)|T0], N1, N, T) :- 1260 !, 1261 N2 is max(N0, N1), 1262 join_nl(T0, N2, N, T). 1263join_nl(L, N, N, L). 1264 1265write_nl(0, _) :- !. 1266write_nl(N, Out) :- 1267 nl(Out), 1268 N1 is N - 1, 1269 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>".
1283valid_cdata(Env, String) :- 1284 atomics_to_string(['</', Env, '>'], End), 1285 sub_atom_icasechk(String, _, End), 1286 !, 1287 domain_error(cdata, String). 1288valid_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)
1304html_print_length(List, Len) :- 1305 mailman(List), 1306 ( html_current_option(dialect(xhtml)) 1307 -> xml_header(Hdr), 1308 atom_length(Hdr, L0), 1309 L1 is L0+1 % one for newline 1310 ; L1 = 0 1311 ), 1312 html_print_length(List, L1, Len). 1313 1314html_print_length([], L, L). 1315html_print_length([nl(N)|T], L0, L) :- 1316 !, 1317 join_nl(T, N, Lines, T1), 1318 L1 is L0 + Lines, % assume only \n! 1319 html_print_length(T1, L1, L). 1320html_print_length([mailbox(_, Box)|T], L0, L) :- 1321 !, 1322 ( Box = accept(_, Accepted) 1323 -> html_print_length(Accepted, L0, L1) 1324 ; L1 = L0 1325 ), 1326 html_print_length(T, L1, L). 1327html_print_length([cdata(_, CDATA)|T], L0, L) :- 1328 !, 1329 html_print_length(CDATA, L0, L1), 1330 html_print_length(T, L1, L). 1331html_print_length([H|T], L0, L) :- 1332 atom_length(H, Hlen), 1333 L1 is L0+Hlen, 1334 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.1344reply_html_page(Head, Body) :- 1345 reply_html_page(default, Head, Body). 1346reply_html_page(Style, Head, Body) :- 1347 html_current_option(content_type(Type)), 1348 phrase(page(Style, Head, Body), HTML), 1349 format('Content-type: ~w~n~n', [Type]), 1350 print_html(HTML). 1351 1352 1353 /******************************* 1354 * META-PREDICATE SUPPORT * 1355 *******************************/
html
. For example:
:- html_meta page(html,html,?,?).
1371html_meta(Spec) :- 1372 throw(error(context_error(nodirective, html_meta(Spec)), _)). 1373 1374html_meta_decls(Var, _, _) :- 1375 var(Var), 1376 !, 1377 instantiation_error(Var). 1378html_meta_decls((A,B), (MA,MB), [MH|T]) :- 1379 !, 1380 html_meta_decl(A, MA, MH), 1381 html_meta_decls(B, MB, T). 1382html_meta_decls(A, MA, [MH]) :- 1383 html_meta_decl(A, MA, MH). 1384 1385html_meta_decl(Head, MetaHead, 1386 html_write:html_meta_head(GenHead, Module, Head)) :- 1387 functor(Head, Name, Arity), 1388 functor(GenHead, Name, Arity), 1389 prolog_load_context(module, Module), 1390 Head =.. [Name|HArgs], 1391 maplist(html_meta_decl, HArgs, MArgs), 1392 MetaHead =.. [Name|MArgs]. 1393 1394html_meta_decl(html, :) :- !. 1395html_meta_decl(Meta, Meta). 1396 1397systemterm_expansion((:- html_meta(Heads)), 1398 [ (:- meta_predicate(Meta)) 1399 | MetaHeads 1400 ]) :- 1401 html_meta_decls(Heads, Meta, MetaHeads). 1402 1403:- multifile 1404 html_meta_head/3. 1405 1406html_meta_colours(Head, Goal, built_in-Colours) :- 1407 Head =.. [_|MArgs], 1408 Goal =.. [_|Args], 1409 maplist(meta_colours, MArgs, Args, Colours). 1410 1411meta_colours(html, HTML, Colours) :- 1412 !, 1413 html_colours(HTML, Colours). 1414meta_colours(I, _, Colours) :- 1415 integer(I), I>=0, 1416 !, 1417 Colours = meta(I). 1418meta_colours(_, _, classify). 1419 1420html_meta_called(Head, Goal, Called) :- 1421 Head =.. [_|MArgs], 1422 Goal =.. [_|Args], 1423 meta_called(MArgs, Args, Called, []). 1424 1425meta_called([], [], Called, Called). 1426meta_called([html|MT], [A|AT], Called, Tail) :- 1427 !, 1428 phrase(called_by(A), Called, Tail1), 1429 meta_called(MT, AT, Tail1, Tail). 1430meta_called([0|MT], [A|AT], [A|CT0], CT) :- 1431 !, 1432 meta_called(MT, AT, CT0, CT). 1433meta_called([I|MT], [A|AT], [A+I|CT0], CT) :- 1434 integer(I), I>0, 1435 !, 1436 meta_called(MT, AT, CT0, CT). 1437meta_called([_|MT], [_|AT], Called, Tail) :- 1438 !, 1439 meta_called(MT, AT, Called, Tail). 1440 1441 1442:- html_meta 1443 html( , , ), 1444 page( , , ), 1445 page( , , , ), 1446 page( , , , , ), 1447 pagehead( , , , ), 1448 pagebody( , , , ), 1449 reply_html_page( , ), 1450 reply_html_page( , , ), 1451 html_post( , , , ). 1452 1453 1454 /******************************* 1455 * PCE EMACS SUPPORT * 1456 *******************************/ 1457 1458:- multifile 1459 prolog_colour:goal_colours/2, 1460 prolog_colour:style/2, 1461 prolog_colour:message//1, 1462 prolog:called_by/2. 1463 1464prolog_colourgoal_colours(Goal, Colours) :- 1465 html_meta_head(Goal, _Module, Head), 1466 html_meta_colours(Head, Goal, Colours). 1467prolog_colourgoal_colours(html_meta(_), 1468 built_in-[meta_declarations([html])]). 1469 1470 % TBD: Check with do_expand! 1471html_colours(Var, classify) :- 1472 var(Var), 1473 !. 1474html_colours(\List, built_in-[built_in-Colours]) :- 1475 is_list(List), 1476 !, 1477 list_colours(List, Colours). 1478html_colours(\_, built_in-[dcg]) :- !. 1479html_colours(_:Term, built_in-[classify,Colours]) :- 1480 !, 1481 html_colours(Term, Colours). 1482html_colours(&(Entity), functor-[entity(Entity)]) :- !. 1483html_colours(List, list-ListColours) :- 1484 List = [_|_], 1485 !, 1486 list_colours(List, ListColours). 1487html_colours(Term, TermColours) :- 1488 compound(Term), 1489 compound_name_arguments(Term, Name, Args), 1490 Name \== '.', 1491 !, 1492 ( Args = [One] 1493 -> TermColours = html(Name)-ArgColours, 1494 ( layout(Name, _, empty) 1495 -> attr_colours(One, ArgColours) 1496 ; html_colours(One, Colours), 1497 ArgColours = [Colours] 1498 ) 1499 ; Args = [AList,Content] 1500 -> TermColours = html(Name)-[AColours, Colours], 1501 attr_colours(AList, AColours), 1502 html_colours(Content, Colours) 1503 ; TermColours = error 1504 ). 1505html_colours(_, classify). 1506 1507list_colours(Var, classify) :- 1508 var(Var), 1509 !. 1510list_colours([], []). 1511list_colours([H0|T0], [H|T]) :- 1512 !, 1513 html_colours(H0, H), 1514 list_colours(T0, T). 1515list_colours(Last, Colours) :- % improper list 1516 html_colours(Last, Colours). 1517 1518attr_colours(Var, classify) :- 1519 var(Var), 1520 !. 1521attr_colours([], classify) :- !. 1522attr_colours(Term, list-Elements) :- 1523 Term = [_|_], 1524 !, 1525 attr_list_colours(Term, Elements). 1526attr_colours(Name=Value, built_in-[html_attribute(Name), VColour]) :- 1527 !, 1528 attr_value_colour(Value, VColour). 1529attr_colours(NS:Term, built_in-[ html_xmlns(NS), 1530 html_attribute(Name)-[classify] 1531 ]) :- 1532 compound(Term), 1533 compound_name_arity(Term, Name, 1). 1534attr_colours(Term, html_attribute(Name)-[VColour]) :- 1535 compound(Term), 1536 compound_name_arity(Term, Name, 1), 1537 !, 1538 Term =.. [Name,Value], 1539 attr_value_colour(Value, VColour). 1540attr_colours(Name, html_attribute(Name)) :- 1541 atom(Name), 1542 !. 1543attr_colours(Term, classify) :- 1544 compound(Term), 1545 compound_name_arity(Term, '.', 2), 1546 !. 1547attr_colours(_, error). 1548 1549attr_list_colours(Var, classify) :- 1550 var(Var), 1551 !. 1552attr_list_colours([], []). 1553attr_list_colours([H0|T0], [H|T]) :- 1554 attr_colours(H0, H), 1555 attr_list_colours(T0, T). 1556 1557attr_value_colour(Var, classify) :- 1558 var(Var). 1559attr_value_colour(location_by_id(ID), sgml_attr_function-[Colour]) :- 1560 !, 1561 location_id(ID, Colour). 1562attr_value_colour(A+B, sgml_attr_function-[CA,CB]) :- 1563 !, 1564 attr_value_colour(A, CA), 1565 attr_value_colour(B, CB). 1566attr_value_colour(encode(_), sgml_attr_function-[classify]) :- !. 1567attr_value_colour(Atom, classify) :- 1568 atomic(Atom), 1569 !. 1570attr_value_colour([_|_], classify) :- !. 1571attr_value_colour(_Fmt-_Args, classify) :- !. 1572attr_value_colour(Term, classify) :- 1573 compound(Term), 1574 compound_name_arity(Term, '.', 2), 1575 !. 1576attr_value_colour(_, error). 1577 1578location_id(ID, classify) :- 1579 var(ID), 1580 !. 1581location_id(ID, Class) :- 1582 ( catch(http_dispatch:http_location_by_id(ID, Location), _, fail) 1583 -> Class = http_location_for_id(Location) 1584 ; Class = http_no_location_for_id(ID) 1585 ). 1586location_id(_, classify). 1587 1588 1589:- op(990, xfx, :=). % allow compiling without XPCE 1590:- op(200, fy, @). 1591 1592prolog_colourstyle(html(_), [colour(magenta4), bold(true)]). 1593prolog_colourstyle(entity(_), [colour(magenta4)]). 1594prolog_colourstyle(html_attribute(_), [colour(magenta4)]). 1595prolog_colourstyle(html_xmlns(_), [colour(magenta4)]). 1596prolog_colourstyle(sgml_attr_function, [colour(blue)]). 1597prolog_colourstyle(http_location_for_id(_), [bold(true)]). 1598prolog_colourstyle(http_no_location_for_id(_), [colour(red), bold(true)]). 1599 1600 1601prolog_colourmessage(html(Element)) --> 1602 [ '~w: SGML element'-[Element] ]. 1603prolog_colourmessage(entity(Entity)) --> 1604 [ '~w: SGML entity'-[Entity] ]. 1605prolog_colourmessage(html_attribute(Attr)) --> 1606 [ '~w: SGML attribute'-[Attr] ]. 1607prolog_colourmessage(sgml_attr_function) --> 1608 [ 'SGML Attribute function'-[] ]. 1609prolog_colourmessage(http_location_for_id(Location)) --> 1610 [ 'ID resolves to ~w'-[Location] ]. 1611prolog_colourmessage(http_no_location_for_id(ID)) --> 1612 [ '~w: no such ID'-[ID] ]. 1613 1614 1615% prolog:called_by(+Goal, -Called) 1616% 1617% Hook into library(pce_prolog_xref). Called is a list of callable 1618% or callable+N to indicate (DCG) arglist extension. 1619 1620 1621prologcalled_by(Goal, Called) :- 1622 html_meta_head(Goal, _Module, Head), 1623 html_meta_called(Head, Goal, Called). 1624 1625called_by(Term) --> 1626 called_by(Term, _). 1627 1628called_by(Var, _) --> 1629 { var(Var) }, 1630 !, 1631 []. 1632called_by(\G, M) --> 1633 !, 1634 ( { is_list(G) } 1635 -> called_by(G, M) 1636 ; {atom(M)} 1637 -> [(M:G)+2] 1638 ; [G+2] 1639 ). 1640called_by([], _) --> 1641 !, 1642 []. 1643called_by([H|T], M) --> 1644 !, 1645 called_by(H, M), 1646 called_by(T, M). 1647called_by(M:Term, _) --> 1648 !, 1649 ( {atom(M)} 1650 -> called_by(Term, M) 1651 ; [] 1652 ). 1653called_by(Term, M) --> 1654 { compound(Term), 1655 !, 1656 Term =.. [_|Args] 1657 }, 1658 called_by(Args, M). 1659called_by(_, _) --> 1660 []. 1661 1662:- multifile 1663 prolog:hook/1. 1664 1665prologhook(body(_,_,_)). 1666prologhook(body(_,_,_,_)). 1667prologhook(head(_,_,_)). 1668prologhook(head(_,_,_,_)). 1669 1670 1671 /******************************* 1672 * MESSAGES * 1673 *******************************/ 1674 1675:- multifile 1676 prolog:message/3. 1677 1678prologmessage(html(expand_failed(What))) --> 1679 [ 'Failed to translate to HTML: ~p'-[What] ]. 1680prologmessage(html(wrong_encoding(Stream, Enc))) --> 1681 [ 'XHTML demands UTF-8 encoding; encoding of ~p is ~w'-[Stream, Enc] ]. 1682prologmessage(html(multiple_receivers(Id))) --> 1683 [ 'html_post//2: multiple receivers for: ~p'-[Id] ]. 1684prologmessage(html(no_receiver(Id))) --> 1685 [ 'html_post//2: no receivers for: ~p'-[Id] ]
Write HTML text
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. */