This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
yap-6.3/packages/http/html_write.pl
2010-06-23 11:52:34 +01:00

1279 lines
33 KiB
Prolog

/* $Id$
Part of SWI-Prolog
Author: Jan Wielemaker and Anjo Anjewierden
E-mail: J.Wielemaker@cs.vu.nl
WWW: http://www.swi-prolog.org
Copyright (C): 1985-2010, University of Amsterdam
VU University Amsterdam
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
As a special exception, if you link this library with other files,
compiled with a Free Software compiler, to produce an executable, this
library does not by itself cause the resulting executable to be covered
by the GNU General Public License. This exception does not however
invalidate any other reasons why the executable file might be covered by
the GNU General Public License.
*/
:- module(html_write,
[ reply_html_page/2, % :Head, :Body
reply_html_page/3, % +Style, :Head, :Body
% Basic output routines
page//1, % :Content
page//2, % :Head, :Body
html//1, % :Content
% Option processing
html_set_options/1, % +OptionList
html_current_option/1, % ?Option
% repositioning HTML elements
html_post//2, % +Id, :Content
html_receive//1, % +Id
html_receive//2, % +Id, :Handler
xhtml_ns//2, % +Id, +Value
% Useful primitives for expanding
html_begin//1, % +EnvName[(Attribute...)]
html_end//1, % +EnvName
html_quoted//1, % +Text
html_quoted_attribute//1, % +Attribute
% Emitting the HTML code
print_html/1, % +List
print_html/2, % +Stream, +List
html_print_length/2 % +List, -Length
]).
:- use_module(library(error)).
:- use_module(library(lists)).
:- use_module(library(option)).
:- use_module(library(pairs)).
:- use_module(library(sgml)). % Quote output
:- use_module(library(url)).
:- use_module(library(quintus)). % for meta_predicate/1
:- set_prolog_flag(generate_debug_info, false).
:- meta_predicate
reply_html_page(+, :, :),
reply_html_page(:, :),
html(:, -, +),
page(:, -, +),
page(:, :, -, +),
pagehead(+, :, -, +),
pagebody(+, :, -, +),
html_receive(+, 3, -, +),
html_post(+, :, -, +).
/** <module> 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:
* It is a lot of typing
* It does not guarantee proper HTML syntax. You have to deal
with HTML quoting, proper nesting and reasonable layout.
* It is hard to use satisfactory abstraction
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.
==
Content-type: text/html; charset=UTF-8
==
When generating XHTML documents, the output stream must be in UTF-8
encoding.
*/
/*******************************
* SETTINGS *
*******************************/
%% html_set_options(+Options) is det.
%
% Set options for the HTML output. Options are stored in prolog
% flags to ensure with proper multi-threaded behaviour where
% setting an option is local to the thread and new threads start
% with the options from the parent thread. Defined options are:
%
% * dialect(Dialect)
% One of =html= (default) or =xhtml=.
%
% * doctype(+DocType)
% Set the =|<|DOCTYPE|= DocType =|>|= line for page//1 and
% page//2.
%
% * content_type(+ContentType)
% Set the =|Content-type|= for reply_html_page/3
%
% Note that the doctype is covered by two prolog flags:
% =html_doctype= for the html dialect and =xhtml_doctype= for the
% xhtml dialect. Dialect muct be switched before doctype.
html_set_options(Options) :-
must_be(list, Options),
set_options(Options).
set_options([]).
set_options([H|T]) :-
html_set_option(H),
set_options(T).
html_set_option(dialect(Dialect)) :- !,
must_be(oneof([html,xhtml]), Dialect),
set_prolog_flag(html_dialect, Dialect).
html_set_option(doctype(Atom)) :- !,
must_be(atom, Atom),
( current_prolog_flag(html_dialect, html)
-> set_prolog_flag(html_doctype, Atom)
; set_prolog_flag(xhtml_doctype, Atom)
).
html_set_option(content_type(Atom)) :- !,
must_be(atom, Atom),
( current_prolog_flag(html_dialect, html)
-> set_prolog_flag(html_content_type, Atom)
; set_prolog_flag(xhtml_content_type, Atom)
).
html_set_option(O) :-
domain_error(html_option, O).
%% html_current_option(?Option) is nondet.
%
% True if Option is an active option for the HTML generator.
html_current_option(dialect(Dialect)) :-
current_prolog_flag(html_dialect, Dialect).
html_current_option(doctype(DocType)) :-
( current_prolog_flag(html_dialect, html)
-> current_prolog_flag(html_doctype, DocType)
; current_prolog_flag(xhtml_doctype, DocType)
).
html_current_option(content_type(ContentType)) :-
( current_prolog_flag(html_dialect, html)
-> current_prolog_flag(html_content_type, ContentType)
; current_prolog_flag(xhtml_content_type, ContentType)
).
option_default(html_dialect, html).
option_default(html_doctype,
'HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" \
"http://www.w3.org/TR/html4/loose.dtd"').
option_default(xhtml_doctype,
'html PUBLIC "-//W3C//DTD XHTML 1.0 \
Transitional//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"').
option_default(html_content_type, 'text/html').
option_default(xhtml_content_type, 'application/xhtml+xml; charset=UTF-8').
%% init_options is det.
%
% Initialise the HTML processing options.
init_options :-
( option_default(Name, Value),
( current_prolog_flag(Name, _)
-> true
; create_prolog_flag(Name, Value, [])
),
fail
; true
).
:- init_options.
%% xml_header(-Header)
%
% First line of XHTML document. Added by print_html/1.
xml_header('<?xml version=\'1.0\' encoding=\'UTF-8\'?>').
%% ns(?Which, ?Atom)
%
% Namespace declarations
ns(xhtml, 'http://www.w3.org/1999/xhtml').
/*******************************
* PAGE *
*******************************/
%% page(+Content:dom)// is det.
%% page(+Head:dom, +Body:dom)// is det.
%
% Generate a page including the HTML =|<!DOCTYPE>|= header. The
% actual doctype is read from the option =doctype= as defined by
% html_set_options/1.
page(Content) -->
doctype,
html(html(Content)).
page(Head, Body) -->
page(default, Head, Body).
page(Style, Head, Body) -->
doctype,
html_begin(html),
pagehead(Style, Head),
pagebody(Style, Body),
html_end(html).
%% doctype//
%
% Emit the =|<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 ...
doctype -->
{ html_current_option(doctype(DocType)),
DocType \== ''
}, !,
[ '<!DOCTYPE ', DocType, '>' ].
doctype -->
[].
pagehead(_, Head) -->
{ functor(Head, head, _)
}, !,
html(Head).
pagehead(Style, Head) -->
{ strip_module(Head, M, _),
hook_module(M, HM, head//2)
},
HM:head(Style, Head), !.
pagehead(_, Head) -->
{ strip_module(Head, M, _),
hook_module(M, HM, head//1)
},
HM:head(Head), !.
pagehead(_, Head) -->
html(head(Head)).
pagebody(_, Body) -->
{ functor(Body, body, _)
}, !,
html(Body).
pagebody(Style, Body) -->
{ strip_module(Body, M, _),
hook_module(M, HM, body//2)
},
HM:body(Style, Body), !.
pagebody(_, Body) -->
{ strip_module(Body, M, _),
hook_module(M, HM, body//1)
},
HM:body(Body), !.
pagebody(_, Body) -->
html(body(Body)).
hook_module(M, M, PI) :-
current_predicate(M:PI), !.
hook_module(_, user, PI) :-
current_predicate(user:PI).
%% html(+Content:dom)// is det
%
% Generate HTML from Content. Generates a token sequence for
% print_html/2.
html(Spec) -->
{ strip_module(Spec, M, T) },
html(T, M).
html([], _) --> !,
[].
html([H|T], M) --> !,
html_expand(H, M),
html(T, M).
html(X, M) -->
html_expand(X, M).
html_expand(M:Term, _) --> !,
html(Term, M).
html_expand(Term, Module) -->
do_expand(Term, Module), !.
html_expand(Term, _Module) -->
{ print_message(error, html(expand_failed(Term))) }.
:- multifile
expand/3.
do_expand(Token, _) --> % call user hooks
expand(Token), !.
do_expand(Fmt-Args, _) --> !,
{ format(string(String), Fmt, Args)
},
html_quoted(String).
do_expand(\List, Module) -->
{ is_list(List)
}, !,
raw(List, Module).
do_expand(\Term, Module, In, Rest) :- !,
call(Module:Term, In, Rest).
do_expand(Module:Term, _) --> !,
html(Term, Module).
do_expand(script(Content), _) --> !, % general CDATA declared content elements?
html_begin(script),
[ Content
],
html_end(script).
do_expand(&(Entity), _) --> !,
{ integer(Entity)
-> format(string(String), '&#~d;', [Entity])
; format(string(String), '&~w;', [Entity])
},
[ String ].
do_expand(Token, _) -->
{ atomic(Token)
}, !,
html_quoted(Token).
do_expand(element(Env, Attributes, Contents), M) --> !,
( { Contents == [],
html_current_option(dialect(xhtml))
}
-> xhtml_empty(Env, Attributes)
; html_begin(Env, Attributes),
html(Contents, M),
html_end(Env)
).
do_expand(Term, M) -->
{ Term =.. [Env, Contents]
}, !,
( { layout(Env, _, empty)
}
-> html_begin(Env, Contents)
; ( { Contents == [],
html_current_option(dialect(xhtml))
}
-> xhtml_empty(Env, [])
; html_begin(Env),
html(Contents, M),
html_end(Env)
)
).
do_expand(Term, M) -->
{ Term =.. [Env, Attributes, Contents],
check_non_empty(Contents, Env, Term)
}, !,
( { Contents == [],
html_current_option(dialect(xhtml))
}
-> xhtml_empty(Env, Attributes)
; html_begin(Env, Attributes),
html(Contents, M),
html_end(Env)
).
check_non_empty([], _, _) :- !.
check_non_empty(_, Tag, Term) :-
layout(Tag, _, empty), !,
print_message(warning, format('Using empty element with content: ~p', [Term])).
check_non_empty(_, _, _).
%% raw(+List, +Modules)// is det.
%
% Emit unquoted (raw) output used for scripts, etc.
raw([], _) -->
[].
raw([H|T], Module) -->
raw_element(H, Module),
raw(T, Module).
raw_element(Var, _) -->
{ var(Var), !,
instantiation_error(Var)
}.
raw_element(\Term, Module, In, Rest) :- !,
call(Module:Term, In, Rest).
raw_element(Fmt-Args, _) --> !,
{ format(string(S), Fmt, Args) },
[S].
raw_element(Value, _) -->
{ must_be(atomic, Value) },
[Value].
%% html_begin(+Env)// is det.
%% html_end(+End)// is det
%
% For html_begin//1, Env is a term Env(Attributes); for
% html_end//1 it is the plain environment name. Used for
% exceptional cases. Normal applications use html//1. The
% following two fragments are identical, where we prefer the first
% as it is more concise and less error-prone.
%
% ==
% html(table(border=1, \table_content))
% ==
% ==
% html_begin(table(border=1)
% table_content,
% html_end(table)
% ==
html_begin(Env) -->
{ Env =.. [Name|Attributes]
},
html_begin(Name, Attributes).
html_begin(Env, Attributes) -->
pre_open(Env),
[<],
[Env],
attributes(Env, Attributes),
( { layout(Env, _, empty),
html_current_option(dialect(xhtml))
}
-> ['/>']
; [>]
),
post_open(Env).
html_end(Env) --> % empty element or omited close
{ layout(Env, _, -),
html_current_option(dialect(html))
; layout(Env, _, empty)
}, !,
[].
html_end(Env) -->
pre_close(Env),
['</'],
[Env],
['>'],
post_close(Env).
%% xhtml_empty(+Env, +Attributes)// is det.
%
% Emit element in xhtml mode with empty content.
xhtml_empty(Env, Attributes) -->
pre_open(Env),
[<],
[Env],
attributes(Attributes),
['/>'].
%% xhtml_ns(Id, Value)//
%
% Demand an xmlns:id=Value in the outer html tag. This uses the
% html_post/2 mechanism to post to the =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.
xhtml_ns(Id, Value) -->
{ html_current_option(dialect(xhtml)) }, !,
html_post(xmlns, \attribute(xmlns:Id=Value)).
xhtml_ns(_, _) -->
[].
%% attributes(+Env, +Attributes)// is det.
%
% Emit attributes for Env. Adds XHTML namespace declaration to the
% html tag if not provided by the caller.
attributes(html, L) --> !,
( { html_current_option(dialect(xhtml)) }
-> ( { option(xmlns(_), L) }
-> attributes(L)
; { ns(xhtml, NS) },
attributes([xmlns(NS)|L])
),
html_receive(xmlns)
; attributes(L),
html_noreceive(xmlns)
).
attributes(_, L) -->
attributes(L).
attributes([]) --> !,
[].
attributes([H|T]) --> !,
attribute(H),
attributes(T).
attributes(One) -->
attribute(One).
attribute(Name=Value) --> !,
[' '], name(Name), [ '="' ],
attribute_value(Value),
['"'].
attribute(NS:Term) --> !,
{ Term =.. [Name, Value]
}, !,
attribute((NS:Name)=Value).
attribute(Term) -->
{ Term =.. [Name, Value]
}, !,
attribute(Name=Value).
attribute(Atom) --> % Value-abbreviated attribute
{ atom(Atom)
},
[ ' ', Atom ].
name(NS:Name) --> !,
[NS, :, Name].
name(Name) -->
[ Name ].
%% attribute_value(+Value) is det.
%
% Print an attribute value. Value is either atomic or one of the
% following terms:
%
% * A+B
% Concatenation of A and B
% * encode(V)
% Emit URL-encoded version of V. See www_form_encode/2.
% * An option list
% Emit ?Name1=encode(Value1)&Name2=encode(Value2) ...
%
% The hook html_write: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.
:- multifile
expand_attribute_value//1.
attribute_value(Var) -->
{ var(Var), !,
instantiation_error(Var)
}.
attribute_value(A+B) --> !,
attribute_value(A),
attribute_value(B).
attribute_value([]) --> !.
attribute_value(List) -->
{ is_list(List) }, !,
[ ? ],
search_parameters(List).
attribute_value(encode(Value)) --> !,
{ www_form_encode(Value, Encoded) },
[ Encoded ].
attribute_value(Value) -->
expand_attribute_value(Value), !.
attribute_value(Value) -->
html_quoted_attribute(Value).
search_parameters([H|T]) -->
search_parameter(H),
( {T == []}
-> []
; [&],
search_parameters(T)
).
search_parameter(Var) -->
{ var(Var), !,
instantiation_error(Var)
}.
search_parameter(Name=Value) -->
{ www_form_encode(Value, Encoded) },
[Name, =, Encoded].
search_parameter(Term) -->
{ Term =.. [Name, Value], !,
www_form_encode(Value, Encoded)
},
[Name, =, Encoded].
search_parameter(Term) -->
{ domain_error(search_parameter, Term)
}.
/*******************************
* QUOTING RULES *
*******************************/
%% html_quoted(Text)// is det.
%
% Quote the value for normal (CDATA) text. Note that text
% appearing in the document structure is normally quoted using
% these rules. I.e. the following emits properly quoted bold text
% regardless of the content of Text:
%
% ==
% html(b(Text))
% ==
%
% @tbd Assumes UTF-8 encoding of the output.
html_quoted(Text) -->
{ xml_quote_cdata(Text, Quoted, utf8) },
[ Quoted ].
%% html_quoted_attribute(+Text)// is det.
%
% Quote the value according to the rules for tag-attributes
% included in double-quotes. Note that -like html_quoted//1-,
% attributed values printed through html//1 are quoted
% atomatically.
%
% @tbd Assumes UTF-8 encoding of the output.
html_quoted_attribute(Text) -->
{ xml_quote_attribute(Text, Quoted, utf8) },
[ Quoted ].
/*******************************
* REPOSITIONING HTML *
*******************************/
%% html_post(+Id, :HTML)// is det.
%
% Reposition HTML to the receiving Id. The http_post//2 call
% processes HTML using html//1. Embedded \-commands are executed
% by mainman/1 from print_html/1 or html_print_length/2. These
% commands are called in the calling context of the html_post//2
% call.
%
% 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)
% ],
% ...)
% ==
html_post(Id, Content) -->
{ strip_module(Content, M, C) },
[ mailbox(Id, post(M, C)) ].
%% html_receive(+Id)// is det.
%
% Receive posted HTML tokens. Unique sequences of tokens posted
% with html_post//2 are inserted at the location where
% html_receive//1 appears.
%
% @see The local predicate sorted_html//1 handles the output of
% html_receive//1.
% @see html_receive//2 allows for post-processing the posted
% material.
html_receive(Id) -->
html_receive(Id, sorted_html).
%% html_receive(+Id, :Handler)// is det.
%
% This extended version of html_receive//1 causes Handler to be
% called to process all messages posted to the channal at the time
% output is generated. Handler is a grammar rule that is called
% with three extra arguments.
%
% 1. A list of Module:Term, of posted terms. Module is the
% contest module of html_post and Term is the unmodified
% term. Members are in the order posted and may contain
% duplicates.
% 2. DCG input list. The final output must be produced by a
% call to html//1.
% 3. DCG output list.
%
% Typically, Handler collects the posted terms, creating a term
% suitable for html//1 and finally calls html//1.
html_receive(Id, Handler) -->
{ strip_module(Handler, M, P) },
[ mailbox(Id, accept(M:P, _)) ].
%% html_noreceive(+Id)// is det.
%
% As html_receive//1, but discard posted messages.
html_noreceive(Id) -->
[ mailbox(Id, ignore(_,_)) ].
%% mailman(+Tokens) is det.
%
% Collect posted tokens and copy them into the receiving
% mailboxes.
mailman(Tokens) :-
memberchk(mailbox(_, accept(_, Accepted)), Tokens),
var(Accepted), !, % not yet executed
mailboxes(Tokens, Boxes),
keysort(Boxes, Keyed),
group_pairs_by_key(Keyed, PerKey),
maplist(mail_id, PerKey).
mailman(_).
mailboxes([], []).
mailboxes([mailbox(Id, Value)|T0], [Id-Value|T]) :- !,
mailboxes(T0, T).
mailboxes([_|T0], T) :-
mailboxes(T0, T).
mail_id(Id-List) :-
mail_handlers(List, Boxes, Content),
( Boxes = [accept(MH:Handler, In)]
-> extend_args(Handler, Content, Goal),
phrase(MH:Goal, In)
; Boxes = [ignore(_, _)|_]
-> true
; Boxes = [accept(_,_),accept(_,_)|_]
-> print_message(error, html(multiple_receivers(Id)))
; print_message(error, html(no_receiver(Id)))
).
mail_handlers([], [], []).
mail_handlers([post(Module,HTML)|T0], H, [Module:HTML|T]) :- !,
mail_handlers(T0, H, T).
mail_handlers([H|T0], [H|T], C) :-
mail_handlers(T0, T, C).
extend_args(Term, Extra, NewTerm) :-
Term =.. [Name|Args],
append(Args, [Extra], NewArgs),
NewTerm =.. [Name|NewArgs].
%% sorted_html(+Content:list)// is det.
%
% Default handlers for html_receive//1. It sorts the posted
% objects to create a unique list.
%
% @bug Elements can differ just on the module. Ideally we
% should phrase all members, sort the list of list of
% tokens and emit the result. Can we do better?
sorted_html(List) -->
{ sort(List, Unique) },
html(Unique).
%% head_html(+Content:list)// is det.
%
% Handler for html_receive(head). Unlike sorted_html//1, it calls
% a user hook html_write:html_head_expansion/2 to process the
% collected head material into a term suitable for html//1.
%
% @tbd This has been added to facilate html_head.pl, an
% experimental library for dealing with css and javascript
% resources. It feels a bit like a hack, but for now I do not know
% a better solution.
head_html(List) -->
{ html_expand_head(List, NewList) },
html(NewList).
:- multifile
html_head_expansion/2.
html_expand_head(List0, List) :-
html_head_expansion(List0, List1),
List0 \== List1, !,
html_expand_head(List1, List).
html_expand_head(List, List).
/*******************************
* LAYOUT *
*******************************/
pre_open(Env) -->
{ layout(Env, N-_, _)
}, !,
[ nl(N) ].
pre_open(_) --> [].
post_open(Env) -->
{ layout(Env, _-N, _)
}, !,
[ nl(N) ].
post_open(_) -->
[].
pre_close(head) --> !,
html_receive(head, head_html),
{ layout(head, _, N-_) },
[ nl(N) ].
pre_close(Env) -->
{ layout(Env, _, N-_)
}, !,
[ nl(N) ].
pre_close(_) -->
[].
post_close(Env) -->
{ layout(Env, _, _-N)
}, !,
[ nl(N) ].
post_close(_) -->
[].
%% layout(+Tag, -Open, -Close) is det.
%
% Define required newlines before and after tags. This table is
% rather incomplete. New rules can be added to this multifile
% predicate.
%
% @param Tag Name of the tag
% @param Open Tuple M-N, where M is the number of lines before
% the tag and N after.
% @param Close Either as Open, or the atom - (minus) to imit the
% close-tag or =empty= to indicate the element has
% no content model.
%
% @tbd Complete table
:- multifile
layout/3.
layout(table, 2-1, 1-2).
layout(blockquote, 2-1, 1-2).
layout(pre, 2-1, 1-2).
layout(center, 2-1, 1-2).
layout(dl, 2-1, 1-2).
layout(ul, 1-1, 1-1).
layout(ol, 2-1, 1-2).
layout(form, 2-1, 1-2).
layout(frameset, 2-1, 1-2).
layout(head, 1-1, 1-1).
layout(body, 1-1, 1-1).
layout(script, 1-1, 1-1).
layout(select, 1-1, 1-1).
layout(map, 1-1, 1-1).
layout(html, 1-1, 1-1).
layout(caption, 1-1, 1-1).
layout(applet, 1-1, 1-1).
layout(tr, 1-0, 0-1).
layout(option, 1-0, 0-1).
layout(li, 1-0, 0-1).
layout(dt, 1-0, -).
layout(dd, 0-0, -).
layout(title, 1-0, 0-1).
layout(h1, 2-0, 0-2).
layout(h2, 2-0, 0-2).
layout(h3, 2-0, 0-2).
layout(h4, 2-0, 0-2).
layout(hr, 1-1, empty). % empty elements
layout(br, 0-1, empty).
layout(img, 0-0, empty).
layout(meta, 1-1, empty).
layout(base, 1-1, empty).
layout(link, 1-1, empty).
layout(input, 0-0, empty).
layout(frame, 1-1, empty).
layout(col, 0-0, empty).
layout(area, 1-0, empty).
layout(input, 1-0, empty).
layout(param, 1-0, empty).
layout(p, 2-1, -). % omited close
layout(td, 0-0, 0-0).
layout(div, 1-0, 0-1).
/*******************************
* PRINTING *
*******************************/
%% print_html(+List) is det.
%% print_html(+Out:stream, +List) is det.
%
% Print list of atoms and layout instructions. Currently used layout
% instructions:
%
% * nl(N)
% Use at minimum N newlines here.
%
% * mailbox(Id, Box)
% Repositioned tokens (see html_post//2 and
% html_receive//2)
print_html(List) :-
current_output(Out),
mailman(List),
write_html(List, Out).
print_html(Out, List) :-
( html_current_option(dialect(xhtml))
-> stream_property(Out, encoding(Enc)),
( Enc == utf8
-> true
; print_message(warning, html(wrong_encoding(Out, Enc)))
),
xml_header(Hdr),
write(Out, Hdr), nl(Out)
; true
),
mailman(List),
write_html(List, Out),
flush_output(Out).
write_html([], _).
write_html([nl(N)|T], Out) :- !,
join_nl(T, N, Lines, T2),
write_nl(Lines, Out),
write_html(T2, Out).
write_html([mailbox(_, Box)|T], Out) :- !,
( Box = accept(_, Accepted)
-> write_html(Accepted, Out)
; true
),
write_html(T, Out).
write_html([H|T], Out) :-
write(Out, H),
write_html(T, Out).
join_nl([nl(N0)|T0], N1, N, T) :- !,
N2 is max(N0, N1),
join_nl(T0, N2, N, T).
join_nl(L, N, N, L).
write_nl(0, _) :- !.
write_nl(N, Out) :-
nl(Out),
N1 is N - 1,
write_nl(N1, Out).
%% html_print_length(+List, -Len) is det.
%
% Determine the content length of a token list produced using
% html//1. Here is an example on how this is used to output an
% HTML compatible to HTTP:
%
% ==
% 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)
% ==
html_print_length(List, Len) :-
mailman(List),
( html_current_option(dialect(xhtml))
-> xml_header(Hdr),
atom_length(Hdr, L0),
L1 is L0+1 % one for newline
; L1 = 0
),
html_print_length(List, L1, Len).
html_print_length([], L, L).
html_print_length([nl(N)|T], L0, L) :- !,
join_nl(T, N, Lines, T1),
L1 is L0 + Lines, % assume only \n!
html_print_length(T1, L1, L).
html_print_length([mailbox(_, Box)|T], L0, L) :- !,
( Box = accept(_, Accepted)
-> html_print_length(Accepted, L0, L1)
; L1 = L0
),
html_print_length(T, L1, L).
html_print_length([H|T], L0, L) :-
atom_length(H, Hlen),
L1 is L0+Hlen,
html_print_length(T, L1, L).
%% reply_html_page(:Head, :Body) is det.
%% reply_html_page(+Style, :Head, :Body) is det.
%
% Provide the complete reply as required by http_wrapper.pl for a
% page constructed from Head and Body. The HTTP =|Content-type|=
% is provided by html_current_option/1.
reply_html_page(Head, Body) :-
reply_html_page(default, Head, Body).
reply_html_page(Style, Head, Body) :-
html_current_option(content_type(Type)),
phrase(page(Style, Head, Body), HTML),
format('Content-type: ~w~n~n', [Type]),
print_html(HTML).
/*******************************
* PCE EMACS SUPPORT *
*******************************/
:- multifile
emacs_prolog_colours:goal_colours/2,
emacs_prolog_colours:style/2,
emacs_prolog_colours:identify/2,
prolog:called_by/2.
emacs_prolog_colours:goal_colours(html(HTML,_,_),
built_in-[Colours, classify, classify]) :-
html_colours(HTML, Colours).
emacs_prolog_colours:goal_colours(page(HTML,_,_),
built_in-[Colours, classify, classify]) :-
html_colours(HTML, Colours).
emacs_prolog_colours:goal_colours(page(Head, Body,_,_),
built_in-[HC, BC, classify, classify]) :-
html_colours(Head, HC),
html_colours(Body, BC).
emacs_prolog_colours:goal_colours(pagehead(HTML,_,_),
built_in-[Colours, classify, classify]) :-
html_colours(HTML, Colours).
emacs_prolog_colours:goal_colours(pagebody(HTML,_,_),
built_in-[Colours, classify, classify]) :-
html_colours(HTML, Colours).
emacs_prolog_colours:goal_colours(reply_html_page(Head, Body),
built_in-[HC, BC]) :-
html_colours(Head, HC),
html_colours(Body, BC).
emacs_prolog_colours:goal_colours(reply_html_page(_Style, Head, Body),
built_in-[identifier, HC, BC]) :-
html_colours(Head, HC),
html_colours(Body, BC).
emacs_prolog_colours:goal_colours(html_post(_Id, HTML, _, _),
built_in-[classify, Colours]) :-
html_colours(HTML, Colours).
% TBD: Check with do_expand!
html_colours(Var, classify) :-
var(Var), !.
html_colours(\List, built_in-Colours) :-
is_list(List), !,
list_colours(List, Colours).
html_colours(\_, built_in-[dcg]) :- !.
html_colours(_:Term, built_in-[classify,Colours]) :- !,
html_colours(Term, Colours).
html_colours(&(Entity), built_in-[entity(Entity)]) :- !.
html_colours(List, built_in-ListColours) :-
List = [_|_], !,
list_colours(List, ListColours).
html_colours(Term, TermColours) :-
compound(Term), !,
Term =.. [Name|Args],
( Args = [One]
-> TermColours = html(Name)-ArgColours,
( layout(Name, _, empty)
-> attr_colours(One, ArgColours)
; html_colours(One, Colours),
ArgColours = [Colours]
)
; Args = [AList,Content]
-> TermColours = html(Name)-[AColours, Colours],
attr_colours(AList, AColours),
html_colours(Content, Colours)
; TermColours = error
).
html_colours(_, classify).
list_colours(Var, classify) :-
var(Var), !.
list_colours([], []).
list_colours([H0|T0], [H|T]) :- !,
html_colours(H0, H),
list_colours(T0, T).
list_colours(Last, Colours) :- % improper list
html_colours(Last, Colours).
attr_colours(Var, classify) :-
var(Var), !.
attr_colours([], classify) :- !.
attr_colours(Term, list-Elements) :-
Term = [_|_], !,
attr_list_colours(Term, Elements).
attr_colours(Name=Value, built_in-[html_attribute(Name), VColour]) :- !,
attr_value_colour(Value, VColour).
attr_colours(NS:Term, built_in-[html_xmlns(NS), html_attribute(Name)-[classify]]) :-
compound(Term),
Term =.. [Name,_], !.
attr_colours(Term, html_attribute(Name)-[VColour]) :-
compound(Term),
Term =.. [Name,Value], !,
attr_value_colour(Value, VColour).
attr_colours(Name, html_attribute(Name)) :-
atom(Name), !.
attr_colours(_, error).
attr_list_colours(Var, classify) :-
var(Var), !.
attr_list_colours([], []).
attr_list_colours([H0|T0], [H|T]) :-
attr_colours(H0, H),
attr_list_colours(T0, T).
attr_value_colour(Var, classify) :-
var(Var).
attr_value_colour(location_by_id(ID), sgml_attr_function-[Colour]) :- !,
location_id(ID, Colour).
attr_value_colour(A+B, sgml_attr_function-[CA,CB]) :- !,
attr_value_colour(A, CA),
attr_value_colour(B, CB).
attr_value_colour(encode(_), sgml_attr_function-[classify]) :- !.
attr_value_colour(Atom, classify) :-
atomic(Atom), !.
attr_value_colour(List, classify) :-
is_list(List), !.
attr_value_colour(_, error).
location_id(ID, classify) :-
var(ID), !.
location_id(ID, Class) :-
current_predicate(http_dispatch:http_location_by_id/2),
( catch(http_dispatch:http_location_by_id(ID, Location), _, fail)
-> Class = http_location_for_id(Location)
; Class = http_no_location_for_id(ID)
).
location_id(_, classify).
:- op(990, xfx, :=). % allow compiling without XPCE
:- op(200, fy, @).
emacs_prolog_colours:style(html(_), style(bold := @on,
colour := magenta4)).
emacs_prolog_colours:style(entity(_), style(colour := magenta4)).
emacs_prolog_colours:style(html_attribute(_), style(colour := magenta4)).
emacs_prolog_colours:style(html_xmlns(_), style(colour := magenta4)).
emacs_prolog_colours:style(sgml_attr_function, style(colour := blue)).
emacs_prolog_colours:style(http_location_for_id(_), style(bold := @on)).
emacs_prolog_colours:style(http_no_location_for_id(_), style(colour := red, bold := @on)).
emacs_prolog_colours:identify(html(Element), Summary) :-
format(string(Summary), '~w: SGML element', [Element]).
emacs_prolog_colours:identify(entity(Entity), Summary) :-
format(string(Summary), '~w: SGML entity', [Entity]).
emacs_prolog_colours:identify(html_attribute(Attr), Summary) :-
format(string(Summary), '~w: SGML attribute', [Attr]).
emacs_prolog_colours:identify(sgml_attr_function, 'SGML Attribute function').
emacs_prolog_colours:identify(http_location_for_id(Location), Summary) :-
format(string(Summary), 'ID resolves to ~w', [Location]).
emacs_prolog_colours:identify(http_no_location_for_id(ID), Summary) :-
format(string(Summary), '~w: no such ID', [ID]).
% prolog:called_by(+Goal, -Called)
%
% Hook into library(pce_prolog_xref). Called is a list of callable
% or callable+N to indicate (DCG) arglist extension.
prolog:called_by(html(HTML,_,_), Called) :-
phrase(called_by(HTML), Called).
prolog:called_by(page(HTML,_,_), Called) :-
phrase(called_by(HTML), Called).
prolog:called_by(page(Head,Body,_,_), Called) :-
phrase(called_by([Head,Body]), Called).
prolog:called_by(pagehead(HTML,_,_), Called) :-
phrase(called_by(HTML), Called).
prolog:called_by(pagebody(HTML,_,_), Called) :-
phrase(called_by(HTML), Called).
prolog:called_by(html_post(_,HTML,_,_), Called) :-
phrase(called_by(HTML), Called).
prolog:called_by(reply_html_page(Head,Body), Called) :-
phrase(called_by([Head,Body]), Called).
prolog:called_by(reply_html_page(_Style,Head,Body), Called) :-
phrase(called_by([Head,Body]), Called).
called_by(Term) -->
called_by(Term, _).
called_by(Var, _) -->
{ var(Var) }, !,
[].
called_by(\G, M) --> !,
( { is_list(G) }
-> called_by(G, M)
; {atom(M)}
-> [M:G+2]
; [G+2]
).
called_by([], _) --> !,
[].
called_by([H|T], M) --> !,
called_by(H, M),
called_by(T, M).
called_by(M:Term, _) --> !,
( {atom(M)}
-> called_by(Term, M)
; []
).
called_by(Term, M) -->
{ compound(Term), !,
Term =.. [_|Args]
},
called_by(Args, M).
called_by(_, _) -->
[].
/*******************************
* MESSAGES *
*******************************/
:- multifile
prolog:message/3.
prolog:message(html(expand_failed(What))) -->
[ 'Failed to translate to HTML: ~p'-[What] ].
prolog:message(html(wrong_encoding(Stream, Enc))) -->
[ 'XHTML demands UTF-8 encoding; encoding of ~p is ~w'-[Stream, Enc] ].
prolog:message(html(multiple_receivers(Id))) -->
[ 'html_post//2: multiple receivers for: ~p'-[Id] ].
prolog:message(html(no_receiver(Id))) -->
[ 'html_post//2: no receivers for: ~p'-[Id] ].