View source with raw comments or as raw
   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(+, 3, -, +),
  96    html_post(+, :, -, +).
  97
  98:- multifile
  99    expand//1,                      % +HTMLElement
 100    expand_attribute_value//1.      % +HTMLAttributeValue
 101
 102
 103/** <module> Write HTML text
 104
 105The purpose of this library  is  to   simplify  writing  HTML  pages. Of
 106course, it is possible to  use  format/3   to  write  to the HTML stream
 107directly, but this is generally not very satisfactory:
 108
 109        * It is a lot of typing
 110        * It does not guarantee proper HTML syntax.  You have to deal
 111          with HTML quoting, proper nesting and reasonable layout.
 112        * It is hard to use satisfactory abstraction
 113
 114This module tries to remedy these problems.   The idea is to translate a
 115Prolog term into  an  HTML  document.  We   use  DCG  for  most  of  the
 116generation.
 117
 118---++ International documents
 119
 120The library supports the generation of international documents, but this
 121is currently limited to using UTF-8 encoded HTML or XHTML documents.  It
 122is strongly recommended to use the following mime-type.
 123
 124==
 125Content-type: text/html; charset=UTF-8
 126==
 127
 128When generating XHTML documents, the output stream must be in UTF-8
 129encoding.
 130*/
 131
 132
 133                 /*******************************
 134                 *            SETTINGS          *
 135                 *******************************/
 136
 137%!  html_set_options(+Options) is det.
 138%
 139%   Set options for the HTML output.   Options  are stored in prolog
 140%   flags to ensure proper multi-threaded behaviour where setting an
 141%   option is local to the thread  and   new  threads start with the
 142%   options from the parent thread. Defined options are:
 143%
 144%     * dialect(Dialect)
 145%       One of =html4=, =xhtml= or =html5= (default). For
 146%       compatibility reasons, =html= is accepted as an
 147%       alias for =html4=.
 148%
 149%     * doctype(+DocType)
 150%       Set the =|<|DOCTYPE|= DocType =|>|= line for page//1 and
 151%       page//2.
 152%
 153%     * content_type(+ContentType)
 154%       Set the =|Content-type|= for reply_html_page/3
 155%
 156%   Note that the doctype and  content_type   flags  are  covered by
 157%   distinct  prolog  flags:  =html4_doctype=,  =xhtml_doctype=  and
 158%   =html5_doctype= and similar for the   content  type. The Dialect
 159%   must be switched before doctype and content type.
 160
 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).
 194
 195%!  html_current_option(?Option) is nondet.
 196%
 197%   True if Option is an active option for the HTML generator.
 198
 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').
 231
 232%!  init_options is det.
 233%
 234%   Initialise the HTML processing options.
 235
 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.
 247
 248%!  xml_header(-Header)
 249%
 250%   First line of XHTML document.  Added by print_html/1.
 251
 252xml_header('<?xml version=\'1.0\' encoding=\'UTF-8\'?>').
 253
 254%!  ns(?Which, ?Atom)
 255%
 256%   Namespace declarations
 257
 258ns(xhtml, 'http://www.w3.org/1999/xhtml').
 259
 260
 261                 /*******************************
 262                 *             PAGE             *
 263                 *******************************/
 264
 265%!  page(+Content:dom)// is det.
 266%!  page(+Head:dom, +Body:dom)// is det.
 267%
 268%   Generate a page including the   HTML  =|<!DOCTYPE>|= header. The
 269%   actual doctype is read from the   option =doctype= as defined by
 270%   html_set_options/1.
 271
 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).
 286
 287%!  doctype//
 288%
 289%   Emit the =|<DOCTYPE ...|= header.  The   doctype  comes from the
 290%   option doctype(DOCTYPE) (see html_set_options/1).   Setting  the
 291%   doctype to '' (empty  atom)   suppresses  the header completely.
 292%   This is to avoid a IE bug in processing AJAX output ...
 293
 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).
 364
 365%!  html(+Content:dom)// is det
 366%
 367%   Generate HTML from Content.  Generates a token sequence for
 368%   print_html/2.
 369
 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).
 496
 497%!  raw(+List, +Module)// is det.
 498%
 499%   Emit unquoted (raw) output used for scripts, etc.
 500
 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].
 530
 531
 532%!  html_begin(+Env)// is det.
 533%!  html_end(+End)// is det
 534%
 535%   For  html_begin//1,  Env  is   a    term   Env(Attributes);  for
 536%   html_end//1  it  is  the  plain    environment  name.  Used  for
 537%   exceptional  cases.  Normal  applications    use   html//1.  The
 538%   following two fragments are identical, where we prefer the first
 539%   as it is more concise and less error-prone.
 540%
 541%   ==
 542%           html(table(border=1, \table_content))
 543%   ==
 544%   ==
 545%           html_begin(table(border=1)
 546%           table_content,
 547%           html_end(table)
 548%   ==
 549
 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).
 581
 582%!  xhtml_empty(+Env, +Attributes)// is det.
 583%
 584%   Emit element in xhtml mode with empty content.
 585
 586xhtml_empty(Env, Attributes) -->
 587    pre_open(Env),
 588    [<],
 589    [Env],
 590    attributes(Attributes),
 591    ['/>'].
 592
 593%!  xhtml_ns(+Id, +Value)//
 594%
 595%   Demand an xmlns:id=Value in the outer   html  tag. This uses the
 596%   html_post/2 mechanism to  post  to   the  =xmlns=  channel. Rdfa
 597%   (http://www.w3.org/2006/07/SWD/RDFa/syntax/), embedding RDF   in
 598%   (x)html provides a typical  usage  scenario   where  we  want to
 599%   publish the required namespaces in the header. We can define:
 600%
 601%   ==
 602%   rdf_ns(Id) -->
 603%           { rdf_global_id(Id:'', Value) },
 604%           xhtml_ns(Id, Value).
 605%   ==
 606%
 607%   After which we can use rdf_ns//1 as  a normal rule in html//1 to
 608%   publish namespaces from library(semweb/rdf_db).   Note that this
 609%   macro only has effect if  the  dialect   is  set  to =xhtml=. In
 610%   =html= mode it is silently ignored.
 611%
 612%   The required =xmlns= receiver  is   installed  by  html_begin//1
 613%   using the =html= tag and thus is   present  in any document that
 614%   opens the outer =html= environment through this library.
 615
 616xhtml_ns(Id, Value) -->
 617    { html_current_option(dialect(xhtml)) },
 618    !,
 619    html_post(xmlns, \attribute(xmlns:Id=Value)).
 620xhtml_ns(_, _) -->
 621    [].
 622
 623%!  html_root_attribute(+Name, +Value)//
 624%
 625%   Add an attribute to the  HTML  root   element  of  the page. For
 626%   example:
 627%
 628%     ==
 629%         html(div(...)),
 630%         html_root_attribute(lang, en),
 631%         ...
 632%     ==
 633
 634html_root_attribute(Name, Value) -->
 635    html_post(html_begin, \attribute(Name=Value)).
 636
 637%!  attributes(+Env, +Attributes)// is det.
 638%
 639%   Emit attributes for Env. Adds XHTML namespace declaration to the
 640%   html tag if not provided by the caller.
 641
 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 ].
 694
 695%!  attribute_value(+Value) is det.
 696%
 697%   Print an attribute value. Value is either   atomic or one of the
 698%   following terms:
 699%
 700%     * A+B
 701%     Concatenation of A and B
 702%     * encode(V)
 703%     Emit URL-encoded version of V.  See www_form_encode/2.
 704%     * An option list
 705%     Emit ?Name1=encode(Value1)&Name2=encode(Value2) ...
 706%     * A term Format-Arguments
 707%     Use format/3 and emit the result as quoted value.
 708%
 709%   The hook html_write:expand_attribute_value//1 can  be defined to
 710%   provide additional `function like'   translations.  For example,
 711%   http_dispatch.pl  defines  location_by_id(ID)  to   refer  to  a
 712%   location on the current server  based   on  the  handler id. See
 713%   http_location_by_id/2.
 714
 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    ;   ['&amp;'],
 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    }.
 781
 782%!  attribute_value_m(+List)//
 783%
 784%   Used for multi-valued attributes, such as class-lists.  E.g.,
 785%
 786%     ==
 787%           body(class([c1, c2]), Body)
 788%     ==
 789%
 790%     Emits =|<body class="c1 c2"> ...|=
 791
 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                 *******************************/
 806
 807%!  html_quoted(Text)// is det.
 808%
 809%   Quote  the  value  for  normal  (CDATA)  text.  Note  that  text
 810%   appearing in the document  structure   is  normally quoted using
 811%   these rules. I.e. the following emits  properly quoted bold text
 812%   regardless of the content of Text:
 813%
 814%   ==
 815%           html(b(Text))
 816%   ==
 817%
 818%   @tbd    Assumes UTF-8 encoding of the output.
 819
 820html_quoted(Text) -->
 821    { xml_quote_cdata(Text, Quoted, utf8) },
 822    [ Quoted ].
 823
 824%!  html_quoted_attribute(+Text)// is det.
 825%
 826%   Quote the value  according  to   the  rules  for  tag-attributes
 827%   included in double-quotes.  Note   that  -like  html_quoted//1-,
 828%   attributed   values   printed   through   html//1   are   quoted
 829%   atomatically.
 830%
 831%   @tbd    Assumes UTF-8 encoding of the output.
 832
 833html_quoted_attribute(Text) -->
 834    { xml_quote_attribute(Text, Quoted, utf8) },
 835    [ Quoted ].
 836
 837%!  cdata_element(?Element)
 838%
 839%   True when Element contains declared CDATA   and thus only =|</|=
 840%   needs to be escaped.
 841
 842cdata_element(script).
 843cdata_element(style).
 844
 845
 846                 /*******************************
 847                 *      REPOSITIONING HTML      *
 848                 *******************************/
 849
 850%!  html_post(+Id, :HTML)// is det.
 851%
 852%   Reposition HTML to  the  receiving   Id.  The  html_post//2 call
 853%   processes HTML using html//1. Embedded   \-commands are executed
 854%   by mailman/1 from  print_html/1   or  html_print_length/2. These
 855%   commands are called in the calling   context of the html_post//2
 856%   call.
 857%
 858%   A typical usage scenario is to  get   required  CSS links in the
 859%   document head in a reusable fashion. First, we define css//1 as:
 860%
 861%   ==
 862%   css(URL) -->
 863%           html_post(css,
 864%                     link([ type('text/css'),
 865%                            rel('stylesheet'),
 866%                            href(URL)
 867%                          ])).
 868%   ==
 869%
 870%   Next we insert the _unique_ CSS links, in the pagehead using the
 871%   following call to reply_html_page/2:
 872%
 873%   ==
 874%           reply_html_page([ title(...),
 875%                             \html_receive(css)
 876%                           ],
 877%                           ...)
 878%   ==
 879
 880html_post(Id, Content) -->
 881    { strip_module(Content, M, C) },
 882    [ mailbox(Id, post(M, C)) ].
 883
 884%!  html_receive(+Id)// is det.
 885%
 886%   Receive posted HTML tokens. Unique   sequences  of tokens posted
 887%   with  html_post//2  are  inserted   at    the   location   where
 888%   html_receive//1 appears.
 889%
 890%   @see    The local predicate sorted_html//1 handles the output of
 891%           html_receive//1.
 892%   @see    html_receive//2 allows for post-processing the posted
 893%           material.
 894
 895html_receive(Id) -->
 896    html_receive(Id, sorted_html).
 897
 898%!  html_receive(+Id, :Handler)// is det.
 899%
 900%   This extended version of html_receive//1   causes  Handler to be
 901%   called to process all messages posted to the channal at the time
 902%   output  is  generated.  Handler  is    called  as  below,  where
 903%   `PostedTerms` is a list of  Module:Term   created  from calls to
 904%   html_post//2. Module is the context module of html_post and Term
 905%   is the unmodified term. Members  in   `PostedTerms`  are  in the
 906%   order posted and may contain duplicates.
 907%
 908%     ==
 909%       phrase(Handler, PostedTerms, HtmlTerms, Rest)
 910%     ==
 911%
 912%   Typically, Handler collects the posted   terms,  creating a term
 913%   suitable for html//1 and finally calls html//1.
 914
 915html_receive(Id, Handler) -->
 916    { strip_module(Handler, M, P) },
 917    [ mailbox(Id, accept(M:P, _)) ].
 918
 919%!  html_noreceive(+Id)// is det.
 920%
 921%   As html_receive//1, but discard posted messages.
 922
 923html_noreceive(Id) -->
 924    [ mailbox(Id, ignore(_,_)) ].
 925
 926%!  mailman(+Tokens) is det.
 927%
 928%   Collect  posted  tokens  and  copy    them  into  the  receiving
 929%   mailboxes. Mailboxes may produce output for  each other, but not
 930%   cyclic. The current scheme to resolve   this is rather naive: It
 931%   simply permutates the mailbox resolution order  until it found a
 932%   working one. Before that, it puts   =head= and =script= boxes at
 933%   the end.
 934
 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).
 963
 964%!  html_token(?Token, +Tokens) is nondet.
 965%
 966%   True if Token is a token in the  token set. This is like member,
 967%   but the toplevel list may contain cdata(Elem, Tokens).
 968
 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).
 977
 978%!  mailboxes(+Tokens, -MailBoxes) is det.
 979%
 980%   Get all mailboxes from the token set.
 981
 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).
1027
1028
1029%!  mail_handlers(+Boxes, -Handlers, -Posters) is det.
1030%
1031%   Collect all post(Module,HTML) into Posters  and the remainder in
1032%   Handlers.  Handlers  consists  of  accept(Handler,  Tokens)  and
1033%   ignore(_,_).
1034
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].
1046
1047%!  sorted_html(+Content:list)// is det.
1048%
1049%   Default  handlers  for  html_receive//1.  It  sorts  the  posted
1050%   objects to create a unique list.
1051%
1052%   @bug    Elements can differ just on the module.  Ideally we
1053%           should phrase all members, sort the list of list of
1054%           tokens and emit the result.  Can we do better?
1055
1056sorted_html(List) -->
1057    { sort(List, Unique) },
1058    html(Unique).
1059
1060%!  head_html(+Content:list)// is det.
1061%
1062%   Handler for html_receive(head). Unlike  sorted_html//1, it calls
1063%   a user hook  html_write:html_head_expansion/2   to  process  the
1064%   collected head material into a term suitable for html//1.
1065%
1066%   @tbd  This  has  been  added   to  facilitate  html_head.pl,  an
1067%   experimental  library  for  dealing  with   css  and  javascript
1068%   resources. It feels a bit like a hack, but for now I do not know
1069%   a better solution.
1070
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    [].
1127
1128%!  layout(+Tag, -Open, -Close) is det.
1129%
1130%   Define required newlines before and after   tags.  This table is
1131%   rather incomplete. New rules can  be   added  to  this multifile
1132%   predicate.
1133%
1134%   @param Tag      Name of the tag
1135%   @param Open     Tuple M-N, where M is the number of lines before
1136%                   the tag and N after.
1137%   @param Close    Either as Open, or the atom - (minus) to omit the
1138%                   close-tag or =empty= to indicate the element has
1139%                   no content model.
1140%
1141%   @tbd    Complete table
1142
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                 *******************************/
1203
1204%!  print_html(+List) is det.
1205%!  print_html(+Out:stream, +List) is det.
1206%
1207%   Print list of atoms and layout instructions.  Currently used layout
1208%   instructions:
1209%
1210%           * nl(N)
1211%           Use at minimum N newlines here.
1212%
1213%           * mailbox(Id, Box)
1214%           Repositioned tokens (see html_post//2 and
1215%           html_receive//2)
1216
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).
1270
1271%!  valid_cdata(+Env, +String) is det.
1272%
1273%   True when String is valid content for   a  CDATA element such as
1274%   =|<script>|=. This implies  it   cannot  contain  =|</script/|=.
1275%   There is no escape for this and  the script generator must use a
1276%   work-around using features of the  script language. For example,
1277%   when  using  JavaScript,  "</script>"   can    be   written   as
1278%   "<\/script>".
1279%
1280%   @see write_json/2, js_arg//1.
1281%   @error domain_error(cdata, String)
1282
1283valid_cdata(Env, String) :-
1284    atomics_to_string(['</', Env, '>'], End),
1285    sub_atom_icasechk(String, _, End),
1286    !,
1287    domain_error(cdata, String).
1288valid_cdata(_, _).
1289
1290%!  html_print_length(+List, -Len) is det.
1291%
1292%   Determine the content length of  a   token  list  produced using
1293%   html//1. Here is an example on  how   this  is used to output an
1294%   HTML compatible to HTTP:
1295%
1296%   ==
1297%           phrase(html(DOM), Tokens),
1298%           html_print_length(Tokens, Len),
1299%           format('Content-type: text/html; charset=UTF-8~n'),
1300%           format('Content-length: ~d~n~n', [Len]),
1301%           print_html(Tokens)
1302%   ==
1303
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).
1335
1336
1337%!  reply_html_page(:Head, :Body) is det.
1338%!  reply_html_page(+Style, :Head, :Body) is det.
1339%
1340%   Provide the complete reply as required  by http_wrapper.pl for a
1341%   page constructed from Head and   Body. The HTTP =|Content-type|=
1342%   is provided by html_current_option/1.
1343
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                 *******************************/
1356
1357%!  html_meta(+Heads) is det.
1358%
1359%   This directive can be used  to   declare  that an HTML rendering
1360%   rule takes HTML content as  argument.   It  has  two effects. It
1361%   emits  the  appropriate  meta_predicate/1    and  instructs  the
1362%   built-in editor (PceEmacs) to provide   proper colouring for the
1363%   arguments.  The  arguments  in  Head  are    the   same  as  for
1364%   meta_predicate or can be constant =html=.  For example:
1365%
1366%     ==
1367%     :- html_meta
1368%           page(html,html,?,?).
1369%     ==
1370
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
1397system:term_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(html,?,?),
1444    page(html,?,?),
1445    page(html,html,?,?),
1446    page(+,html,html,?,?),
1447    pagehead(+,html,?,?),
1448    pagebody(+,html,?,?),
1449    reply_html_page(html,html),
1450    reply_html_page(+,html,html),
1451    html_post(+,html,?,?).
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_colour:goal_colours(Goal, Colours) :-
1465    html_meta_head(Goal, _Module, Head),
1466    html_meta_colours(Head, Goal, Colours).
1467prolog_colour:goal_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_colour:style(html(_),                    [colour(magenta4), bold(true)]).
1593prolog_colour:style(entity(_),                  [colour(magenta4)]).
1594prolog_colour:style(html_attribute(_),          [colour(magenta4)]).
1595prolog_colour:style(html_xmlns(_),              [colour(magenta4)]).
1596prolog_colour:style(sgml_attr_function,         [colour(blue)]).
1597prolog_colour:style(http_location_for_id(_),    [bold(true)]).
1598prolog_colour:style(http_no_location_for_id(_), [colour(red), bold(true)]).
1599
1600
1601prolog_colour:message(html(Element)) -->
1602    [ '~w: SGML element'-[Element] ].
1603prolog_colour:message(entity(Entity)) -->
1604    [ '~w: SGML entity'-[Entity] ].
1605prolog_colour:message(html_attribute(Attr)) -->
1606    [ '~w: SGML attribute'-[Attr] ].
1607prolog_colour:message(sgml_attr_function) -->
1608    [ 'SGML Attribute function'-[] ].
1609prolog_colour:message(http_location_for_id(Location)) -->
1610    [ 'ID resolves to ~w'-[Location] ].
1611prolog_colour:message(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
1621prolog:called_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
1665prolog:hook(body(_,_,_)).
1666prolog:hook(body(_,_,_,_)).
1667prolog:hook(head(_,_,_)).
1668prolog:hook(head(_,_,_,_)).
1669
1670
1671                 /*******************************
1672                 *            MESSAGES          *
1673                 *******************************/
1674
1675:- multifile
1676    prolog:message/3.
1677
1678prolog:message(html(expand_failed(What))) -->
1679    [ 'Failed to translate to HTML: ~p'-[What] ].
1680prolog:message(html(wrong_encoding(Stream, Enc))) -->
1681    [ 'XHTML demands UTF-8 encoding; encoding of ~p is ~w'-[Stream, Enc] ].
1682prolog:message(html(multiple_receivers(Id))) -->
1683    [ 'html_post//2: multiple receivers for: ~p'-[Id] ].
1684prolog:message(html(no_receiver(Id))) -->
1685    [ 'html_post//2: no receivers for: ~p'-[Id] ].