The Anubis Project.
Making interactive Web sites.
Copyright (c) Alain Prouté 2004-2005.
Authors: Alain Prouté
Last revision: January 2005.
In this file we propose simple tools for making well structured interactive and secured
web sites.
----------------------------------- Table of Contents ---------------------------------
*** (1) Structure of a web site.
*** (1.1) Overview.
*** (1.2) What web pages are made of.
*** (1.3) Actions.
*** (1.4) States.
*** (2) Carrying on.
*** (2.1) Describing your web sites.
*** (2.2) Directories on the server's disk.
*** (2.3) Starting your web sites.
*** (3) The HTML interface.
*** (3.1) Types used by the HTML interface.
*** (3.2) ``in form'' versus ``off form''.
*** (3.3) Defining your own style.
*** (3.4) Actioners and forms.
*** (3.5) Local popup.
---------------------------------------------------------------------------------------
read tools/basis.anubis
read web/common.anubis
read web/multihost_http_server.anubis
*** (1) Structure of a web site.
First of all we need to explain what a web site should be made of. Ideally, the client
should see the web site working as any other interactive computer software. So, it
should be clear that a 'session' (i.e. a visit to the web site, including the
consultation of several pages) is some kind of conversation between the client and the
web site, and that the web site should maintain a 'current state' of this conversation.
At each new request (click) from the client, this state must be updated.
*** (1.1) Overview.
The data which are stored into the state should be considered as 'session data'. They
do not encompass 'permanent data', which are typically stored into a data base. We
want to separate the following two functionalities (which are used at each request
(click) during a single session):
- computing the new state from the previous state and from the client request,
- computing the page to be sent to the client from the new current state.
The next picture shows the structure we have in mind:
request +---------+ HTML page (with a hidden state name)
.-------------------| client |<--------------.
| .-----------------| | |
| | previous state +---------+ |
| | name |
| | | client side
............................................................................
| | | server side
| | |
| | .-------------------. |
| | | previous state | |
V V V | |
+---------------+ +---------------+ +--------------+
| compute state | | server's disk | | compute page |
+---------------+ +---------------+ +--------------+
^ | | ^ ^ ^ ^
| | | | | | |
| | `--------------------+--------------------' |
| | new state | |
read | `------------------------+--------------------'
write | new state name
update V
+-----------+
| data base |
+-----------+
When the client begins a session, there is no previous state. In this case, a default
'initial state' is used instead.
It is important that computing the HTML page is performed only from the new state (and
its name) not using the data base. This is because the data base may have been updated
by another client since the computation of the new state. Doing another way could
create an inconsistency between the page sent to the client and its current state
recorded on the server's disk.
In this file, all the above stuff is defined. You just have to provide the function for
computing a new state (compute state) and the function for computing the page (compute
page) from the new state. You don't have to worry about state names, saving and
retrieving states and the like.
*** (1.2) What web pages are made of.
What the client can see in his browser's window may be called a 'page'. Within a page,
we have several sorts of components:
- 'local' components, i.e. all components which do not open a connection, like
texts, images, etc... possibly using JavaScript programmation,
- 'actioners', which, when clicked upon, open a connection with our web site; they
may appear as links or buttons, etc...
- 'foreign links', which when clicked upon, open a connection with another web site.
Of course, what an actioner does is just ask our web site to perform an action. To that
end, the actioner essentially sends the name of the action to be performed. However, it
may be necessary to provide additional informations which may be seen as 'operands' of
the action. In order to attach operands to an action, HTML provides the notion of
'form'. Indeed, a form contains essentially a set of input fields into which the client
may put values for the required operands of the action, and a submit button, which is
the actioner itself. Notice that a single form may contain several submit buttons,
which simply means that there are several distincts actions taking the same set of
operands.
Restrictions must be put on the use of all theses gadgets. Indeed, for example,
putting a form within another form is meaningless. HTML does not explicitly forbid such
things, but the client's browser may be seriously disturbed. In this file, we propose
an interface to the HTML language, which forbids such meaningless things, simply by
imposing a strict typing of HTML concepts.
Each web site may be accessible through two communication channels:
- a non secured channel (HTTP),
- a secured channel (HTTPS).
Nevertheless, the whole thing should be considered as a single web site. For example,
you may have a secured page, obtained through HTTPS, containing public images obtained
through HTTP. An actioner in a non secured page may open a secured connection, and
conversely.
Summarizing, a web page is made of local elements, foreign links and actioners.
Actioners receive operands from forms, and they also choose to communicate through the
non secured or through the secured channel.
+-------------------+
| page |
| | +---------------+
| +--------------+ | | next page |
| | form | | | (non secured) |
| | +----------+ | | HTTP | |
| | | actioner |---------------------------->| |
| | +----------+ | | +---------------+
| | | |
| | +----------+ | | +---------------+
| | | actioner |---------------------------->| next page |
| | +----------+ | | HTTPS | (secured) |
| | | | | |
| +--------------+ | | |
| | +---------------+
| |
+-------------------+
*** (1.3) Actions.
The client opens a new connection with our web site whenever he clicks on an
actioner. The result is that a request is sent, essentially made of a list of 'web
arguments'. Each web argument is a pair (name,value). One of these web arguments, the
'action' web argument (whose name is "a"), determines the action to be performed. The
other web arguments (not including "s") are the operands for this action.
Hence, the 'compute state' box in the picture above, splits naturally into as many
sub-boxes as there are actions. For this reason, we define the following type for
representing actions (where '$State' is the type representing session informations):
public type ActionTicket:... // a definitely opaque type
public type Web_Action($State):
http_action (String name, // name of action
(ActionTicket,
$State) -> Bool allow, // true if action allowed
(ActionTicket ticket,
HTTP_Info http_info,
List(Web_arg) web_args, // actually only 'operands' web arguments
$State state) -> $State do_it),
https_action (String name, // name of action
(ActionTicket,
$State) -> Bool allow, // true if action allowed
(ActionTicket ticket,
HTTP_Info http_info,
List(Web_arg) web_args, // actually only 'operands' web arguments
$State state) -> $State do_it),
http_https_action (String name,
(ActionTicket,
$State) -> Bool allow,
(ActionTicket ticket,
HTTP_Info http_info,
List(Web_arg) web_args,
$State state) -> $State do_it).
'HTTP actions' are executed only under HTTP, and 'HTTPS actions' are executed only
under HTTPS.
Each action has a name, which is used to identify the action. Each action also has a
function 'allow' whose job is to verify that the action is allowed in the current
state, and a function 'do_it' for performing the action. The function 'do_it' receives
a lot of informations:
- 'HTTP informations':
- the IP address of the client,
- the URI requested by the client (after redirection),
- the list of HTTP headers generated by the client's browser,
- the list of web arguments sent by the client (except "s" and "a"),
- the previous state (or the 'initial' or 'ticket expired' state if no previous
state can be found).
In most cases, HTTP informations are not used. This is the reason why they are gathered
for simplicity into a unique datum of type 'HTTP_Info'.
For your convenience, we introduce the following simpler variants:
public define Web_Action($State)
http_action
(
String name,
$State -> Bool allow,
(List(Web_arg),$State) -> $State do_it
) =
http_action(name,
(ActionTicket t, $State s) |-> allow(s),
(ActionTicket t, HTTP_Info h, List(Web_arg) l, $State s) |-> do_it(l,s)).
public define Web_Action($State)
https_action
(
String name,
$State -> Bool allow,
(List(Web_arg),$State) -> $State do_it
) =
https_action(name,
(ActionTicket t, $State s) |-> allow(s),
(ActionTicket t, HTTP_Info h, List(Web_arg) l, $State s) |-> do_it(l,s)).
public define Web_Action($State)
http_https_action
(
String name,
$State -> Bool allow,
(List(Web_arg),$State) -> $State do_it
) =
http_https_action(name,
(ActionTicket t, $State s) |-> allow(s),
(ActionTicket t, HTTP_Info h, List(Web_arg) l, $State s) |-> do_it(l,s)).
When you define your web site, you must provide the list of all the actions of the
site. When a new state has been computed, a graphical representation of this state
must be sent to the client. To that end, you must provide a function (named below
'compute_page') of type:
$State -> HTML_Page
where the type 'HTML_Page' (defined below in this file) abstractly represents HTML
pages.
public type HTML_Page:...
It should be clear that states and pages are deeply linked together. Indeed, we really
understand the page shown to the client as a representation of the current state of the
conversation between the client and the web site.
*** (1.4) States.
Now, we explain how to define the type (say 'State') to be used as an instance of the
type parameter '$State'.
Each state determines a page (since 'compute_page' computes a page from a
state). However, some components of the state may be independant of the page. It may be
the case for example for the indication of the natural language used by the
client. Hence, a state should be made of (at least) two parts:
- informations which are the same for all pages,
- informations which are particular to each page.
For example, you could define:
type Page: // one alternative per page, with particular informations
login(...), // in the components
main_page(...),
...etc...
Now, the type 'State' could be defined as follows:
type State:
state(Language, // informations valid for all pages
...,
Page). // informations particular to a page
However, if you are making a secured web site within which clients should be identified
(by id and password), it may be a good idea to have two sorts of states, one for non
identified clients and one for identified clients. In this case, define the type
'State' as follows (this is just a suggestion):
type State:
non_identified(Language),
identified(String id,
Language,
Page).
When a request arrives, check if the previous state is 'identified(...)' or
'non_identified(...)', and don't provide access to certain pages to non identified
clients. This is required for security.
Some more words on security. If your site needs to identify clients, define the
initial state as 'non_identified(...)'. Construct a 'login' page, and check the id and
password of the client. If the id and password are correct, then change the state of
the client to 'identified(...)'. No other action should be able to do that. Now, be
confident that clients cannot forge states. The only information they have is the name
of a state, not the state itself which is never sent over the network, but only stored
on the server's disk. The name of the state is constructed using strong cryptographical
methods (sha1). If everything (since the 'login' page) is performed under HTTPS, even
state names cannot be seen by a third party. So, if the system retrieves a previous
state of the form 'identified(...)', you can be confident that your client is well
identified, and you can send him confidential informations.
States have a limited life time. It may happen that a client clicks on a button at a
time its state is out of date. In this case, this system considers that the new state
is a special state named 'ticket expired'. You must provide a function producing this
state when you describe your web site. The page corresponding to this state must just
inform the client that he/she waited a too long time before clicking on a button, and
has to restart (a new conversation) from the begining.
*** (2) Carrying on.
*** (2.1) Describing your web sites.
Before you may start your web site, you must describe it, i.e. produce a datum of the
opaque type 'Web_Site'.
public type Web_Site:...
Producing such a datum may be performed by:
public define Web_Site
make_web_site_description
(
String common_name, // for example: "www.our-business.com"
String site_directory, // where 'public' and other directories are
// located (should NOT end with '/')
ActionTicket -> One init,
(ActionTicket,
HTTP_Info) -> $State initial_state,
(ActionTicket,
$State expired,
HTTP_Info,
List(Web_arg),
Bool is_https) -> $State ticket_expired_state,
(ActionTicket,
HTTP_Info,
List(Web_arg),
Bool is_https) -> $State ticket_lost_state,
List(Web_Action($State)) actions,
$State -> HTML_Page compute_page,
Int32 timeout, // seconds (todo: minutes)
List(Redirection) redirections,
List(String) journal_extensions,
List(String) journal_headers,
String authorization_secret,
List(MIME) known_mime_types,
(String action_name,
List(Web_arg) args)-> One before_send_file
).
Explanations:
'common_name' is the name of the site (the name the browser must send as the value of
the 'Host' HTTP header in order to access the site). Such a name generally looks like
this:
www.somewhere.com
If you are using HTTPS, you also have an 'X.509 SSL server certificate'. The name of
the site must be exactly the same as the name on the certificate (which is precisely
called the 'common name' in the X.509 jargon). If the two names do not match, the site
will still work, but the transaction will not be transparent to the client. His browser
will complain that the name of the certificate does not match the name of the site, and
he will have to accept the certificate manually.
'site_directory' is the absolute path to the directory where the files needed by the
site are located. Usually this directory looks like:
my_anubis/web_sites/www.somewhere.com
However, this information is not computed from 'common_name', so that you can change
the common name (for example temporarily, for networking reasons) without loosing
access to the files.
'ticket_expired_state(expired_state,http_info,lwa,is_https)' must produce the state
whose graphical representation is a page explaining to the user that its 'ticket' (or
'session information') has expired, and that he/she must close all popup windows and
start a new session. The arguments of the function contain the previous (expired)
state and all current informations concerning the user. This arguments may be useful
for example for producing the expiration message in the language chosen by the user.
You can also (and this may be much smarter) send a 'ticket prolongation page'
(including a new login for example), and resume the same conversation, since you have
all the pertinent informations at hand. In the case the ticket is definitely lost, the
second fonction 'ticket_lost_state' is used.
Notice that despite the fact that the parameter $State is involved in the arguments of
the above function, the type 'Web_Site' does not depend on this parameter. This allows
to produce lists of web site descriptions, where each description may be constructed
with a different instance of $State. This is required because distinct sites must have
distinct types of session informations. This is made possible by the fact that the
type is obscure, and the constructor replaced by a function which assembles
'ticket_expired_state', ticket_lost_state', 'actions' and 'compute_page' into a single
entity not depending on $State. You should have a look to the private part of this file
if you want more precisions about this programming technique.
'before_send_file' is a function which is executed just before the HTTP server sends a
file. It gets an action name and the web arguments received with the request for that
file. Notice that this action name and these web arguments may be put into a
'private_download' element, and will come back to the server at the time of the
download.
*** (2.2) Directories on the server's disk.
The description of you site contains the name of the directory within which the
required files are located. This may be for example:
my_anubis/web_sites/www.our-business.com/
This is called the 'site directory' (for the given site). Within the site directory,
the following directories are created by this program:
states
public
journal
private_download
upload_temporary
The directory 'states' is used for storing states (session informations). Out of date
states are automatically removed after some time.
The tree rooted at 'public' contains files that the server is allowed to send to the
clients. For security reasons, the server never sends a file which is not within the
tree whose root is this 'public' directory (except for the 'private download' mecanism;
see 'web/multihost_http_server.anubis'). Also, the MIME type (see 'web/mime.anubis')
must have been recognized before the file may be sent.
The directory 'journal' contains the jounal files. The roles of the remaining
directories 'private_download' and 'upload_temporary' is explained in
'multihost_http_server.anubis', where you will also find further informations on
'public' and 'journal'.
*** (2.3) Starting your web sites.
When you have described all your web sites (you may want to have several web sites, and
they are distinguished by their 'common name'), you may start them all together using
'start_web_sites' below. This function returns a result of the following type:
public type Start_Web_Sites_Result:
cannot_bind_to_port(Int32),
cannot_bind_to_port(Int32,Int32),
ok(Server http_server,
Server https_server).
Indeed, it may happen that the system cannot bind (begin to listen) to one of the two
ports (or to both). The main reason is that another server is already listening on that
port. Another reason may be that 'anbexec' has not been correctly installed, i.e. that
the 's' bit has not been set for 'user' and 'group' (there is not such problem under
Windows). Also notice that the Linux kernel may need a rather long time (up to several
minutes) before liberating a listening port. Now, if the system can bind to the two
ports, the pair of the two servers is returned. Two tools are useful for manipulating
servers:
shutdown of type Server -> One
is_down of type Server -> Bool
They are defined in 'predefined.anubis' (together with the type 'Server').
public define Start_Web_Sites_Result
start_web_sites
(
Int32 ip_address, // the IP address shared by the web sites
Int32 http_port, // usually: 80
Int32 https_port, // usually: 443
String ssl_certificate_common_name,
List(Web_Site) web_sites // web sites to be started
).
'ip_address' is the IP address on which the two servers listen. If you put 0, the
servers listen on all the IP addresses of the machine. This may be useful if the
machine has several network interfaces.
'ssl_certificate_common_name' is the common name of the SSL certificate that 'anbexec'
loads when it starts. One instance of 'anbexec' cannot handle more than one SSL server
certificate. This is due to a problem of conception of SSL itself. See the book 'SSL
and TLS' by Eric Rescorla (at Addison Wesley) for more explanations.
Notice that the number of servers is always 2, regardless of the number of web sites
you are starting.
*** (3) The HTML interface.
We propose an interface to dynamic HTML. Dynamic HTML includes HTML, and a combination
of CSS (Cascading Style Sheet) and JavaScript techniques for making HTML elements more
reactive and attractive on the client side.
*** (3.1) Types used by the HTML interface.
For easy reference, we gather below the definitions of all the types used by the HTML
interface, and we comment them immediately.
public type Text_Option:
size(Int32), // size of character font to use
font(String), // name of character font to use (such as "helvetica",...)
color(RGB), // color to be used for characters
italic,
oblique,
small_capitals,
bold,
underlined,
left_justified,
right_justified,
justified, // justified on both sides
line_through.
A list of 'Text_Option' must be given with each text you want to put in your page.
public type Table_Option:
background_color(RGB), // applied to all cells in the table
background_image(String url),
border(Int32 width_of_outer_edge, // if not present, all values are 0
Int32 width_of_top_of_relief,
Int32 width_of_inner_edge,
RGB border_color),
width(Int32), // sets a minimal width for the table
percentage_width(Int32).
public define Table_Option nude = border(0,0,0,rgb(0,0,0)).
A list of 'Table_Option' must be given with each table.
public type BackgroundOption:
repeat, // repeat the background in both directions
repeat_horizontal, // repeat the background only horizontally
repeat_vertical, // repeat the background only verticall
no_repeat. // don't repeat the background
public type Cell_Option:
left, // put the content of the cell on the left
h_center, // center the content of the cell horizontally
right, // put the content of the cell on the right
top, // put the content of the cell upwards
v_center, // center the content of tye cell vertically,
bottom, // put the content of the cell downwards
base_line, // align the content vertically according to base lines
background_color(RGB),
background_image(String url, BackgroundOption),
width(Int32), // sets a minimal width for the cell
percentage_width(Int32),
height(Int32), // sets a minimal height for the cell
columns(Int32), // lets the cell span over several columns
rows(Int32), // lets the cell span over several rows
nowrap. // do not allow text wrapping within the cell
A list of 'Cell_Option' must be given with each cell and each row in a table. Options
given with a row apply to all the cells in the row, but are superseded by options given
with cells, which apply only to the cell they are given with.
public type HTML_Cell($T):
cell(List(Cell_Option) options, $T content).
The parameter $T is later instantiated either to 'HTML_In_Form' or to 'HTML_Off_Form',
depending on where you put your table (within a form or not within a form). For your
convenience, we define the following particular case:
public define HTML_Cell($T)
cell
(
$T content
) =
cell([],content).
public type HTML_Row($T):
row(List(Cell_Option) options, List(HTML_Cell($T)) cells).
Same remark as for 'HTML_Cell($T)'. We define several convenience functions:
public define HTML_Row($T)
row
(
List(HTML_Cell($T)) cells
) =
row([],cells).
public define HTML_Row($T)
row
(
HTML_Cell($T) cell
) =
row([],[cell]).
public type Actioner_Connection:
same, // use same type of connection as current page
http, // use non secured connection
https. // use secured connection
public type Other_Window_Option:
resizable, // the new window may be resized by the client
scrollbars, // the new window has scrollbars
width(Int32), // the new window has the specified width
height(Int32). // the new window has the specified height
public type Actioner_Target:
same,
other(String window_name, List(Other_Window_Option)).
public type Actioner_Aspect:
link (List(Text_Option),String text), // hypertext link
button (String url_off, String url_on), // rollover button
button (String text, Int32 width, RGB color), // rollover button
immediate_selector (String name, Int32 size, List(String) choices).
public define Actioner_Aspect
link
(
String text
) =
link([],text).
Actioners are explained in details below.
public type TextAreaOption:
disabled.
public type HTML_In_Form:
literal (Printable_tree),
sequence (List(HTML_In_Form) items),
text (List(Text_Option), String the_text),
preformated (List(Text_Option), String),
paragraph (List(Text_Option), String the_text),
image (String url),
table (List(Table_Option), List(HTML_Row(HTML_In_Form))),
center (HTML_In_Form),
mail_to (String email, HTML_In_Form element),
scroller (Int32 width, Int32 height,
Int32 content_width, Int32 content_height,
HTML_In_Form content),
actioner (Actioner_Connection, Actioner_Target, Actioner_Aspect,
String action_name, List((String,String)) extra_ops),
local_popup (Actioner_Aspect, HTML_In_Form content,
Int32 x, Int32 y, String title, RGB color, Int32 width),
foreign_link (String url, String name),
private_download (String abs_path, String name, String extra_ext,
Maybe((String,List((String,String)))) action),
text_input (String name, String init, Int32 width),
password_input (String name, Int32 width),
text_area (List(TextAreaOption), String name, String init, Int32 width, Int32 height),
file_upload (String name, Int32 width),
selector (String name, Int32 size, List(String) choices),
selector (String name, Int32 size, List(String) choices, String selected),
radio_button (String name, String value, Bool checked),
check_box (String name, Bool checked).
'HTML_In_Form' defines all the elements you may put within a form. We define a
convenience function:
public define HTML_In_Form
text_area
(
String name,
String init,
Int32 width,
Int32 height
) =
text_area([],name,init,width,height).
public define HTML_In_Form
table
(
List(HTML_Row(HTML_In_Form)) rows
) =
table([],rows).
public define HTML_In_Form
private_download
(
String abs_path,
String name,
String extra_ext
) =
private_download(abs_path,name,extra_ext,failure).
public define HTML_In_Form
private_download
(
String abs_path,
String name,
String extra_ext,
String action_name,
List((String,String)) args
) =
private_download(abs_path,name,extra_ext,success((action_name,args))).
public type HTML_Off_Form:
literal (Printable_tree),
sequence (List(HTML_Off_Form) items),
text (List(Text_Option), String the_text),
preformated (List(Text_Option), String),
paragraph (List(Text_Option), String the_text),
image (String url),
table (List(Table_Option), List(HTML_Row(HTML_Off_Form))),
center (HTML_Off_Form),
mail_to (String email, HTML_Off_Form element),
scroller (Int32 width, Int32 height,
Int32 content_width, Int32 content_height,
HTML_Off_Form content),
fixed_size (Int32 width, Int32 height, HTML_Off_Form content),
fixed_size (Int32 width, Int32 height, String name_of_HTML_file),
actioner (Actioner_Connection, Actioner_Target, Actioner_Aspect,
String action_name, List((String,String)) extra_ops),
actioner (Actioner_Connection, Actioner_Target, Actioner_Aspect,
String action_name, List((String,String)) extra_ops, String form_name),
local_popup (Actioner_Aspect, HTML_Off_Form content,
Int32 x, Int32 y, String title, RGB color, Int32 width),
foreign_link (String url, String name),
private_download (String abs_path, String name, String extra_ext,
Maybe((String,List((String,String)))) action),
form (String form_name, HTML_In_Form content).
'HTML_Off_Form' defines all the elements you may put outside any form.
public define HTML_Off_Form
table
(
List(HTML_Row(HTML_Off_Form)) rows
) =
table([],rows).
We add two convenience functions for 'row'. The reason why we add two functions, one
for 'HTML_In_Form' and one for 'HTML_Off_Form', is that adding a schema with an
arbitrary '$T' creates too many ambiguities. This is due to the fact that, if we do so,
the arguments of the function do not refer to any of the types defined here.
public define HTML_Row(HTML_In_Form)
row
(
HTML_In_Form content
) =
row([],[cell([],content)]).
public define HTML_Row(HTML_Off_Form)
row
(
HTML_Off_Form content
) =
row([],[cell([],content)]).
public define HTML_Off_Form
private_download
(
String abs_path,
String name,
String extra_ext
) =
private_download(abs_path,name,extra_ext,failure).
public define HTML_Off_Form
private_download
(
String abs_path,
String name,
String extra_ext,
String action_name,
List((String,String)) args
) =
private_download(abs_path,name,extra_ext,success((action_name,args))).
Notice that the two types have alternatives in common (same name, same arguments types,
up to the value of the parameter $T), which correspond to elements which may be put
anywhere in the page.
public type HTML_Meta:
keywords (List(String)),
refresh (Actioner_Connection connection,
Actioner_Target target,
String action_name,
Int32 delay), // in seconds
meta (String name, String content),
http_equiv (String name, String content),
generic_meta (List((String,String))).
Meta tags are put in the 'head' of the HTML page.
public type Body_Option:
background_color (RGB),
background_image (String url).
public type HTML_Body:
body(List(Body_Option) options, HTML_Off_Form content).
public type HTML_Page:
html_page(String title, List(HTML_Meta) meta_tags, HTML_Body body).
'HTML_Page' represents the final product of the construction of a web page.
*** (3.2) ``in form'' versus ``off form''.
There is a variety of HTML elements: texts, buttons, links, forms, inputs, etc... Some
of them may have a content, which is yet another HTML element (or several). Hence, it
is meaningful to say that an element is 'within' another one. Now, putting any element
within any other one may be meaningless. For example, a input element must be put
within a form (otherwise, it is useless), and a from within another form has no precise
meaning.
Actually, the main criterium is ``within a form or not within a form''. So, HTML
elements in a given page are separated into two categories: those who are within a
form, and the others. Nevertheless, there are elements which may belong to both
categories, like images and texts. We want to make use of the strong typing mecanism of
Anubis in order to forbid non meaningful placement of elements.
The type 'HTML_In_Form' defines elements to be put within forms. Similarly,
'HTML_Off_Form' defines elements not to be put within forms. Both types are recursive,
and 'HTML_Off_Form' refers to 'HTML_In_Form' (via the 'form' alternative, of course),
but the two types are not cross recursive. This is the reason why it is impossible to
put a form within a form. In order to construct a web page, you essentially have to
produce a datum of type 'HTML_Off_Form' (maybe containing data of type 'HTML_In_Form').
In practice, you don't have to worry so much about these two types, because elements
which may be put anywhere are constructed for both types by functions with the same
name and the same arguments. Hence, for both types, you just write the same thing. You
are warned by the compiler only when you try to put an element at a place it is not
allowed.
*** (3.3) Defining your own style.
We provide generic tools for constructing HTML elements. However, your web site needs
to have a ``style''.
To that end, you need to write down a set of ``styling functions'', using the tools
defined here. These styling functions allow the introduction of your colors and other
visual characteristics into the constructed elements once and for all. For example, you
may want all your texts to be rendered in the ``Helvetica'' font, in size 14 and using
some 'text_color'. You may write something like this:
define RGB text_color = rgb(10,40,40).
define HTML_Off_Form
text
(
String the_text
) =
text([font("helvetica"),size(14),color(text_color)],
the_text).
(and the same one for type 'HTML_In_Form') so that in order to put a piece of text in a
page, you just write:
text("... some text ...")
and you don't have to provide the font, size and color for each text. If you want to
have several styles of text presentation, you just write several sets of such
convenience functions. This also suggests a trick. You may want for example different
colors for 'in form' texts and 'off form' texts. This may be achieved automatically by
defining two functions as above, with the same name and same argument type, but
returning either a 'HTML_In_Form' or a 'HTML_Off_Form'.
If this preliminary work is well done, you will not waste your time later when you
concentrate on the actual informational content of your pages.
This general principle should be applied to all sorts of elements. This is the best
thing to do in order to separate the functions defining the visual style from the
functions defining the informational content itself, so that changing the style without
changing the content becomes easy. This is also the best way for having a clean and
easily readable source for your web site.
*** (3.4) Actioners and forms.
We have gathered several notions from HTML into that of an 'actioner'. An actioner is
an HTML element which opens a connection to our server when clicked upon. Actioners may
have different visual aspects. They may look like hypertext links or like buttons
(rollovers), or even like selectors (with immediate action). In any case, their
behavior is the same: they open a connection to our server, and send a set of 'web
arguments', i.e. pairs 'name=value'. Among these web arguments, one of them denotes
the action to be performed, and the others should be considered as operands for this
action. Actually, the precise behavior of the actioner has several variants.
The connection with the server may be secured (HTTPS) or non secured (HTTP). See the
type 'Actioner_Connection' above.
You must also choose where the answer must be rendered. This may be in the same window
or in another window (or frame). If it is in another window, the name of that window
must be given. If the window does not exist, the browser will create it. Optionally,
you may give the dimensions of the new window and other characteristics. See the type
'Actioner_Target' above.
The actioner also has a visual aspect. See the type 'Actioner_Aspect' above. In the
case of a rollover button, you provide the URLs of two images (of the same size)
representing the button:
url_off: to be used when the mouse is not over the button,
url_on: to be used when the mouse is over the button.
You can also create rollover buttons without creating images. Just use the second
alternative named 'button'. The server creates the images automatically.
The purpose of forms is just to give operands to actioners. If the actioner is placed
within a form, all the input elements which are within this form provide operands to
the actioner (except sometimes when they are not set by the client). If it is not put
within a form, the actioner gets no operand, except if the name of a form is explicitly
given, in which case the actioner gets all the inputs from that form as
operands. Furthermore, you may want to give extra operands to the actioner. This may be
useful for separating families of actioners with the same action name. Extra operands
'name=value' must be given in the form of pairs '(name,value)'.
Notice that the name of a form may be used by an actioner which is off the form, so as
to get the operands provided by this form. Also notice that several actioners may refer
to the same form, being either in the form, or referring to the form from the
outside. These actioners simply get the same set of operands, even if they correspond
to distinct actions.
Input elements may be put only within a form.
*** (3.5) Local popup.
This element looks like a link or a rollover button. When this button is clicked upon,
a 'popup window' appears. Actually, this popup window is just a layer in the same HTML
page, which becomes suddenly visible. It is realized with a '
' HTML tag. In
particular, clicking on the button does not open any connection. This is why it is
called 'local'. The arguments have the following roles:
Actioner_Aspect aspect of the button (same semantics as for actioners)
content content of the popup window
x, y, position of the popup window on the HTML page (not relative
to the button but to the page itself)
title title of the popup window
color color of the title bar and close button in the popup window.
A lightened version of this color is used for the background
of the popup window.
width width of the title bar
--- That's all for the public part ! --------------------------------------------------
----------------------------------- Table of Contents ---------------------------------
*** [1] States.
*** [1.1] Saving and retrieving states.
*** [1.2] Deleting out of date states.
*** [2] Tools.
*** [2.1] Directories.
*** [2.2] Secondary documents.
*** [3] Managing web arguments.
*** [3.1] Prefixing web arguments names.
*** [3.2] Separating web arguments.
*** [3.3] Applying an action.
*** [4] Web site descriptions and the 'awp handlers'.
*** [4.1] The type 'Web_Site'.
*** [4.2] Making a web site description.
*** [4.3] Starting the servers.
*** [5] HTML Formating.
*** [5.1] The type 'HTML_Any($T)'.
*** [5.2] Formating a color.
*** [5.3] Creating buttons.
*** [5.4] Formating an actioner.
*** [5.5] Formating a private download link.
*** [5.6] Formating rows and cells in a table.
*** [5.7] Formating elements which may be put anywhere.
*** [5.8] Formating 'in form' elements.
*** [5.9] Formating 'off form' elements.
*** [5.10] Formating meta-tags.
---------------------------------------------------------------------------------------
*** [1] States.
We have to define functions for saving a state, retrieving a state, deleting out of
date states. We need one such function per web site. The types of the first two
functions depend on the parameter $State. This is not the case of the third one. The
fact that the instance of $State is variable from one web sites to the other implies
rather subtle manipulations using full functionality.
*** [1.1] Saving and retrieving states.
Each state is saved into a file on the server's disk (in the directory represented by
the symbol 'state_directory', which is 'my_anubis/web_sites/common_name/states'). The
state is saved together with a time stamp whose value is obtained by adding the current
time to the given timeout for states. The state receives a name obtained by hashing
(using sha1) the content of the file itself, and then encoding the hash with
'web_arg_encode'. The name of the file into which the state is saved is the
concatenation of "s" and the name of the state.
read web/web_arg_encode.anubis
The tool below constructs the function which is able to save a state on the server's
disk.
define ($State s) -> String // the function constructed returns the name of the state
make_save_state_function
(
Int32 timeout,
String state_directory
) =
($State s) |->
with time_stamp = now+timeout,
to_be_saved = (time_stamp,s),
state_name = web_arg_encode(sha1(to_be_saved)),
if save(to_be_saved,state_directory+"/s"+state_name) is ok
then state_name
else (print("Cannot create state file in '"+state_directory+"'.\n"); "").
When a request arrives, we need to retrieve the previous state from the server's
disk. We receive the name of that state. If the state is out of date, the state file is
kept 3 days, and then deleted.
type PreviousState($State):
not_found, // cannot retrieve the previous state
out_of_date($State), // the previous state is out of date
still_valid($State). // the previous state is still valid
define (String state_name) -> PreviousState($State)
make_retrieve_state_function
(
String state_directory
) =
(String state_name) |->
with file_path = state_directory+"/s"+state_name,
if (RetrieveResult((Int32,$State)))retrieve(file_path) is ok(d)
then (
if d is (time_stamp,s) then
if time_stamp < now
then (
forget(remove(file_path));
out_of_date(s)
)
else still_valid(s) // state has been successfully retrieved
)
else not_found.
*** [1.2] Deleting out of date states.
We also need to delete states which are out of date and which will never be deleted by
the above method. This may be performed by a machine doing this periodically (say once
per states life time period).
define (List(String) file_names) -> One
make_delete_out_of_date_states_function
(
Maybe($State) dummy,
String state_directory
) =
(List(String) file_names) |-df->
if file_names is
{
[ ] then unique,
[h . t] then
with file_path = state_directory+"/"+h,
if (RetrieveResult((Int32,$State)))retrieve(file_path) is ok(d)
then (
if d is (time_stamp,data) then
if time_stamp < now
then (forget(remove(file_path)); df(t))
else df(t)
)
else (forget(remove(file_path)); df(t))
}.
The 'labelled arrow' |-df-> is documented in 'documentation/en/anubis_doc.txt'.
Note: The argument 'dummy' (of type $State) is not used in the body of the function
(hence its name). Nevertheless, it is required. Indeed, the Anubis compiler does not
accept a parameter in the body of a function (here the parameter is required by the use
of 'retrieve') if this parameter does not appear in the type of the function. This is
because this would create ambiguities that no explicit typing may ever resolve. If you
put a double slash in front of the declaration of 'dummy' above, and if you compile
this file, you will get a message like this one:
Error in 'making_a_web_site.anubis', line 805, column 7:
A definition may not contain parameters which are not present
in the declaration part (hidden parameters):
$State
The type of the function constructed by 'make_delete_out_of_date_states_function' is
independant of the parameter $State. This is important because this allows to create
the list of such functions for all web sites. From this list, it is possible to call
the functions one after the other, so deleting out of date states for all web
sites. Actually, the next function receives a list of pairs (state_directory,function),
one for each web site.
define One
delete_out_of_date_states // for all web sites
(
List((String, List(String) -> One)) directories_and_functions
) =
if directories_and_functions is
{
[ ] then unique,
[h . t] then if h is (state_directory,function) then
function(directory_list(state_directory,"s*"));
delete_out_of_date_states(t)
}.
The above function must be called periodically in a separate virtual machine. The
period we have choosen is (rather logically) the life time of states itself. This may
be achieved by an 'infinite' loop, using a 'sleep(timeout)'. However, the loop must not
be really infinite, because the servers may be shutdown. Hence, our loop must test
(rather frequently; say every second) if the servers are down. If they are, the loop
must be exited.
define One
delete_states_loop
(
List((String,List(String) -> One)) directories_and_functions,
Int32 timeout,
Int32 next_time,
Server http_server,
Server https_server
) =
if (is_down(http_server) & is_down(https_server))
then unique
else if now > next_time
then
(
delete_out_of_date_states(directories_and_functions);
delete_states_loop(directories_and_functions,
timeout,
now+timeout,
http_server,
https_server)
)
else
(
sleep(1000); // sleep just one second and try again
delete_states_loop(directories_and_functions,
timeout,
next_time,
http_server,
https_server)
).
The above loop must be run in a separate virtual machine. This will be done just after
the two servers are started.
*** [2] Tools.
*** [2.1] Directories.
We need a tool for creating directories (if needed).
(This tool has been moved to 'tools/basis.anubis').
*** [2.2] Secondary documents.
Some HTML elements (like '
', ' ') cannot receive their content directly
from the current document, but only through an URL. For this reason, we implement a
mecanism for creating secondary documents on the fly. To that end we use the 'private
download' mecanism.
A secondary document is formated by the same functions as the main document itself. The
next function takes an 'off form' element, creates the file containing the secondary
document in HTML format, and returns the URL at which the document will be available.
define String
create_secondary_document
(
String sd, // site directory
String as, // authorization_secret
String sn, // state name
$T -> Printable_tree format_element,
$T content,
Int32 width
) =
with private_download_directory = sd+"/private_download",
hash = web_arg_encode(sha1(content)),
file_content = (Printable_tree)
["",
format_element(content),
"
"
],
file_name = "sd"+hash+".html",
file_path = private_download_directory+"/"+file_name,
if write_to_file(file_path,file_content) is
{
cannot_open_file then print("Cannot open file '"+file_path+"'.\n"); "",
write_error(n) then print("Error writing file '"+file_path+"'.\n"); "",
ok then file_name+"?zauth="+
make_authorization(sd,as,private_download_directory+"/"+file_name)
}.
*** [2.3] Generating unique ids.
In order to uniquely name object for JavaScript we generate unique ids from a counter.
define Int32
new_idnum
(
Var(Int32) ic_v // 'idnum' counter variable
) =
protect
with result = *ic_v+1,
ic_v <- result;
result.
*** [3] Managing web arguments.
Web arguments are those pairs 'name=value' which are transmitted through the HTTP
protocol. We need precise naming conventions for these web arguments.
*** [3.1] Prefixing web arguments names.
We want to assign different roles to web arguments, and we also want to be able to
recognize its role directly from the name of a web argument. The name "s" is reserved
for the web argument whose value is the name of the current state. The name "a" is
reserved for the web argument whose value is the name of the action to be
performed. Other web arguments receive arbitrary names, and in order to avoid clashes,
these names are prefixed by:
"p" for names of password inputs,
"o" for other web arguments
The reason why password input names have a distinct prefix is that this allows the HTTP
server to hide the passwords on the console of the server and in the journal.
*** [3.2] Separating web arguments.
When a new request arrives, we need to separate the web arguments, that is to say:
- find the value of "s", and recover the corresponding state,
- find the value of "a", which is the name of the action to be performed,
- get the list of all the remaining web arguments (operands of the action).
We must also determine if the previous state may be recovered. If it is not the case
(either because the previous state name is invalid, or the previous state is out of
date), we must check if there is an action name. Indeed, the presence of an action name
indicates that the user has clicked on one of our buttons or links. If on the contrary
there is no action name the user has just entered our address in his browser. In this
last case, we must send the first page of our site (maybe a 'login' page), but if there
is an action, we must send a page just saying that the session ticket has expired. If
the previous state is recovered and there is no action, the new state is the same as
the previous state.
The result of the separation of the web arguments is of type:
type Separated_Web_Args($State):
swa(PreviousState($State) previous_state,
Maybe(String) action_name,
List(Web_arg) operands).
The next function constructs the function which separates the web arguments.
define (List(Web_arg) lwa) -> Separated_Web_Args($State)
make_separate_web_args_function
(
String state_directory,
String -> PreviousState($State) retrieve_state
) =
(List(Web_arg) lwa) |-swaf->
if lwa is
{
[ ] then
//
// no web arg found => no previous state and no action
//
swa(not_found,failure,[]),
[wa_1 . wa_others] then
//
// at least one web arg =>
// separate other web args, and insert the first one as needed
//
if (Separated_Web_Args($State))swaf(wa_others) is
{
swa(ps1, // possible previous state
an1, // maybe an action name
op1) // operands so far
then
if wa_1 is
{
web_arg(n,v) then
with prefix = substr(n,0,1),
if prefix = "s" then
swa(retrieve_state(v),an1,op1) else
if prefix = "a" then
swa(ps1,success(v),op1) else
if prefix = "t" then
swa(ps1,an1,[web_arg("&target",v) . op1]) else
if prefix = "p" then
swa(ps1,an1,[web_arg(substr(n,1,length(n)-1),v) . op1]) else
if prefix = "o" then
swa(ps1,an1,[web_arg(substr(n,1,length(n)-1),v) . op1]) else
swa(ps1,an1,op1),
upload(n,v,t) then
swa(ps1,an1,[upload(substr(n,1,length(n)-1),v,t) . op1])
}}
}.
*** [3.3] Applying an action.
When the web arguments are separated (and their names cleaned up from prefixes), we may
apply the action to the operands and the current state. We search for the action to be
applied in the list of actions. If no action is found, the new state is the same as
the previous state. Also, we deny the application of an HTTP action if the request
arrives through the HTTPS channel and conversely.
public type ActionTicket:
hf87jdh23sqPjfYUGF7865GHTjf7.
Preferably, do never use this alternative name explicitely. See 'tools/sdbms.anubis'
for explanations.
define ($State previous,
String action_name,
HTTP_Info http_info,
List(Web_arg) lwa,
Bool is_https) -> $State
make_apply_action_function
(
String common_name,
List(Web_Action($State)) actions
) =
with f =
($State previous,
String action_name,
HTTP_Info http_info,
List(Web_arg) lwa,
Bool is_https,
List(Web_Action($State)) actions) |-f->
if actions is
{
[ ] then (print(common_name+": action '"+action_name+
"' not found.\n"); previous),
[ac1 . others] then if ac1 is
{
http_action(an,allow,do_it) then
if an = action_name
then if is_https
then (print(common_name+": HTTP action '"+an+
"' called through HTTPS (denied).\n");
previous)
else if allow(hf87jdh23sqPjfYUGF7865GHTjf7,previous)
then do_it(hf87jdh23sqPjfYUGF7865GHTjf7,http_info,lwa,previous)
else previous
else f(previous,action_name,http_info,lwa,is_https,others),
https_action(an,allow,do_it) then
if an = action_name
then if is_https
then if allow(hf87jdh23sqPjfYUGF7865GHTjf7,previous)
then do_it(hf87jdh23sqPjfYUGF7865GHTjf7,http_info,lwa,previous)
else previous
else (print(common_name+": HTTPS action '"+an+
"' called through HTTP (denied).\n");
previous)
else f(previous,action_name,http_info,lwa,is_https,others),
http_https_action(an,allow,do_it) then
if an = action_name
then if allow(hf87jdh23sqPjfYUGF7865GHTjf7,previous)
then do_it(hf87jdh23sqPjfYUGF7865GHTjf7,http_info,lwa,previous)
else previous
else f(previous,action_name,http_info,lwa,is_https,others),
}
},
($State previous,
String action_name,
HTTP_Info http_info,
List(Web_arg) lwa,
Bool is_https) |->
f(previous,action_name,http_info,lwa,is_https,actions).
*** [4] Web site descriptions and the 'awp handlers'.
*** [4.1] The type 'Web_Site'.
The type 'Web_Site_Description' is defined in 'web/common.anubis'. We need another one,
because, we have some extra informations to record for each site.
public type Web_Site:
web_site((Int32,Int32) -> Web_Site_Description description,
List(String) -> One delete_out_of_date).
*** [4.2] Making a web site description.
Below is the function which creates a web site description. It first creates (if
needed) the directories for the site, then constructs the tool functions for the site,
and the site handler. Finally, it constructs the web site description.
We gather common (constant) informations in the following type:
type CommonInfo:
info(String common_name,
Int32 http_port,
Int32 https_port,
String site_directory,
String authorization_secret,
SystemFont font).
We need a forward declaration.
public define Printable_tree
format
(
CommonInfo cinfo,
String state_name,
HTML_Page page,
Bool is_https
).
define Printable_tree
top_redirection_page
(
String common_name
) =
[
"",
" ",
""
].
public define Web_Site
make_web_site_description
(
String common_name, // for example: "www.our-business.com"
String site_directory,
ActionTicket -> One init,
(ActionTicket,
HTTP_Info) -> $State initial_state,
(ActionTicket,
$State expired,
HTTP_Info,
List(Web_arg),
Bool is_https) -> $State ticket_expired_state,
(ActionTicket,
HTTP_Info,
List(Web_arg),
Bool is_https) -> $State ticket_lost_state,
List(Web_Action($State)) actions,
$State -> HTML_Page compute_page,
Int32 timeout,
List(Redirection) redirections,
List(String) journal_extensions,
List(String) journal_headers,
String secret,
List(MIME) known_mime_types,
(String action_name,
List(Web_arg) args) -> One before_send_file
) =
init(hf87jdh23sqPjfYUGF7865GHTjf7);
//
// load a font for button texts
//
if load_system_font("times_medium_r_12") is
{
failure then print("Cannot load system font 'times_medium_r_12'.\n"); alert,
success(font) then with
//
// make required directories (if needed)
//
web_sites_directory = make_directory(my_anubis_directory+"/web_sites"),
state_directory = make_directory(site_directory+"/states"),
//
// manage automatic buttons: create the buttons directory (if needed),
// and remove old buttons.
//
buttons_directory = site_directory+"/public/buttons",
forget(make_directory(site_directory+"/public"));
forget(make_directory(buttons_directory));
with button_files = directory_list(buttons_directory,"b*.jpg"),
forget(map((String fn) |-> remove(buttons_directory+"/"+fn), button_files));
//
// construct tool functions
//
with
save_state = make_save_state_function(timeout,state_directory),
retrieve_state = make_retrieve_state_function(state_directory),
separate_web_args = make_separate_web_args_function(state_directory,retrieve_state),
apply_action = make_apply_action_function(common_name,actions),
//
// construct the site handler
//
site_handler = (Int32 http_port, Int32 https_port) |->
((HTTP_Info http_info,
List(Web_arg) lwa,
Bool is_https) |-> (Printable_tree)
if separate_web_args(lwa) is
{
swa(mb_previous_state,mb_action_name,operands) then
with new_state = if mb_previous_state is
{
not_found then
if mb_action_name is
{
failure then ($State) initial_state(hf87jdh23sqPjfYUGF7865GHTjf7,http_info),
success(_) then
ticket_lost_state(hf87jdh23sqPjfYUGF7865GHTjf7,http_info,lwa,is_https)
},
out_of_date(state) then
ticket_expired_state(hf87jdh23sqPjfYUGF7865GHTjf7,state,http_info,lwa,is_https),
still_valid(state) then
if mb_action_name is
{
failure then state,
success(action_name) then
apply_action(state,action_name,http_info,operands,is_https)
}
},
state_name = save_state(new_state),
format(info(common_name,http_port,https_port,site_directory,secret,font),
state_name,compute_page(new_state),is_https)
}),
//
// make the delete_out_of_date function
//
delete_out_of_date =
make_delete_out_of_date_states_function((Maybe($State))failure,
site_directory+"/states"),
//
// construct the web site description
//
web_site((Int32 http_port, Int32 https_port) |->
web_site_description(common_name,
site_directory,
redirections,
journal_extensions,
journal_headers,
secret,
known_mime_types,
site_handler(http_port,https_port),
(List(Web_arg) lwa) |-> if separate_web_args(lwa) is
swa(mb_previous_state,mb_action_name,operands) then
if mb_action_name is
{
failure then unique
success(an) then before_send_file(an,operands)
}),
delete_out_of_date)}.
*** [4.3] Starting the servers.
public define Start_Web_Sites_Result
start_web_sites
(
Int32 ip_address, // the IP address shared by the web sites
Int32 http_port, // usually: 80
Int32 https_port, // usually: 443
String ssl_certificate_common_name,
List(Web_Site) web_sites // web sites to be started
) =
with get_description = (Web_Site ws) |-> description(ws)(http_port,https_port),
with http_server_r =
start_http_server (ip_address,http_port, map(get_description,web_sites)),
with https_server_r =
start_https_server(ip_address,https_port,ssl_certificate_common_name,map(get_description,web_sites)),
if http_server_r is ok(http_server)
then
(
if https_server_r is ok(https_server)
then
(
start_http_servers_tasks(map(get_description,web_sites));
delegate
delete_states_loop(
map((Web_Site ws) |->
(site_directory(description(ws)(http_port,https_port))+
"/states",delete_out_of_date(ws)),
web_sites),
3600*24*3, // keep out of date states 3 days
now,
http_server,
https_server),
ok(http_server,https_server)
)
else cannot_bind_to_port(https_port)
)
else
(
if https_server_r is ok(https_server)
then cannot_bind_to_port(http_port)
else cannot_bind_to_port(http_port,https_port)
).
public define One
start_web_sites
(
Int32 ip_address, // the IP address shared by the web sites
Int32 http_port, // usually: 80
Int32 https_port, // usually: 443
String ssl_certificate_common_name,
List(Web_Site) web_sites // web sites to be started
) =
if (Start_Web_Sites_Result)start_web_sites(ip_address,
http_port,
https_port,
ssl_certificate_common_name,
web_sites) is
{
cannot_bind_to_port(n) then print("Cannot bind to port: "+n+"\n"),
cannot_bind_to_port(n,m) then print("Cannot bind to ports: "+n+", "+m+"\n"),
ok(s1,s2) then print("Servers started.\n")
}.
*** [5] HTML Formating.
We need to translate HTML elements as defined above into actual HTML text.
Actioners require special informations, which must be transmitted when needed by the
'format' functions:
- the 'common name', which is used for URLs,
- the HTTP/HTTPS port number,
- the 'state name', which must be transmitted when the actioner is clicked upon,
- the 'form name' (if any) to which the actioner refers.
If the actioner is off form, and if it refers to a form, the name of that form is
already known by the actioner. On the contrary, if the actioner is 'in form', it refers
implicitly to the form containing it. The name of that form is transmitted to the
'format' functions called from within the formating of that form.
*** [5.1] The type 'HTML_Any($T)'.
The type 'HTML_Any($T)' gathers elements which may be put anywhere in the page. The
parameter $T becomes either 'HTML_Off_Form' or 'HTML_In_Form'.
type HTML_Any($T):
any_text (List(Text_Option), String the_text),
any_preformated (List(Text_Option), String),
any_paragraph (List(Text_Option), String the_text),
any_image (String url),
any_table (List(Table_Option), List(HTML_Row($T))),
any_center ($T),
any_mail_to (String email, $T element),
any_scroller (Int32 width, Int32 height,
Int32 content_width, Int32 content_height,
$T content),
any_fixed_size (Int32 width, Int32 height, $T content),
any_fixed_size (Int32 width, Int32 height, String name_of_HTML_file),
any_actioner (Actioner_Connection,
Actioner_Target,
Actioner_Aspect,
String action_name,
List((String,String)) extra_ops,
Maybe(String) form_name),
any_local_popup (Actioner_Aspect, $T content, Int32 x, Int32 y,
String title, RGB color, Int32 width),
any_foreign_link (String url, String name),
any_private_download (String abs_path, String name, String extra_ext,
Maybe((String,List((String,String))))).
*** [5.2] Formating a color.
RGB colors are formatted as '#rrggbb' where rr, gg and bb are two characters
hexadecimal values.
define String
format
(
RGB color
) =
if color is rgb(r,g,b) then
"#" + hexadecimal(int8_to_int32(r),2)
+ hexadecimal(int8_to_int32(g),2)
+ hexadecimal(int8_to_int32(b),2).
The following is a very arbitrary definition of the opposite color. The thing which is
important is that it is far from the original, so that characters in 'opposite' color
are clearly visible over the original.
define RGB
opposite
(
RGB color
) =
if color is rgb(r,g,b) then
with r1 = int8_to_int32(r),
with g1 = int8_to_int32(g),
with b1 = int8_to_int32(b),
rgb(truncate_to_int8(255-r1),
truncate_to_int8(255-g1),
truncate_to_int8(255-b1)).
*** [5.3] Creating buttons.
We want to be able to create buttons in the form of a pair of images (rollovers)
automatically. We use the JPEG interface, because for the time being Anubis cannot
handle other kinds of images.
Computing printed text length.
define Int32
printed_text_width
(
Int8 -> Int32 char_size,
List(Int8) l
) =
if l is
{
[] then (Int32) 0,
[h . t] then char_size(h) + 1+ printed_text_width(char_size,t)
}.
define Int32
printed_text_width
(
SystemFont font,
String s
) =
printed_text_width((Int8 c) |-> int8_to_int32(width(get_char_info(font,c))),
explode(s)).
Converting RGB to RGBA.
define RGBA
to_rgba
(
RGB color
) =
if color is rgb(r,g,b) then rgba(r,g,b,255).
Drawing a 'relief'.
define One
draw_relief
(
RGBAImage dest,
RGBA color,
Int32 contrast,
Int32 x,
Int32 y,
Int32 width,
Int32 height
) =
with l = lighten(color,contrast),
d = darken(color,contrast),
draw_rectangle(dest,rect(x,y,x+width,y+1),l);
draw_rectangle(dest,rect(x,y+1,x+1,y+height),l);
draw_rectangle(dest,rect(x+width-1,y+1,x+width,y+height),d);
draw_rectangle(dest,rect(x+1,y+height-1,x+width-1,y+height),d).
Creating a button background.
define RGBAImage
create_button_background
(
RGBA color,
Int32 width,
Int32 height
) =
with result = create_rgba_image(width,height,color),
draw_relief(result,color,100,0,0,width,height);
draw_relief(result,color,70,1,1,width-2,height-2);
draw_relief(result,color,55,2,2,width-4,height-4);
draw_relief(result,color,35,3,3,width-6,height-6);
draw_relief(result,color,20,4,4,width-8,height-8);
draw_relief(result,color,10,5,5,width-10,height-10);
draw_relief(result,color,5,6,6,width-12,height-12);
result.
Drawing the text over the background.
define One
draw_button_text
(
RGBAImage image,
String text,
Int32 text_index,
Int32 pixel_x,
Int32 y,
Rectangle clip,
RGBA color,
SystemFont font,
) =
if nth(text_index,text) is
{
failure then unique,
success(c) then
with cw = draw_system_character(image,clip,pixel_x,y,font,int8_to_int32(c),color),
draw_button_text(image,text,text_index+1,pixel_x+cw+1,y,clip,color,font)
}.
define One
draw_button_text
(
RGBAImage image,
String text,
Int32 text_width,
RGBA light_color,
RGBA dark_color,
SystemFont font
) =
with image_width = width(image),
image_height = height(image),
x_pos = (image_width-text_width)>>1,
clip = rect(0,0,image_width,image_height),
new_light_color = lighten(light_color,150),
new_dark_color = darken(dark_color,40),
draw_button_text(image, text, 0, x_pos+2, 16, clip, new_dark_color, font);
draw_button_text(image, text, 0, x_pos, 14, clip, new_light_color, font).
The next function creates the two images for a button. The information given is the
main color of the button, the text of the button and the minimal width (in pixels) of
the button. The function does not create the button if the images already exist. The
two images are stored in the directory 'site_directory/buttons'. The names of the files
are of the form:
bxxxx_off.jpg
bxxxx_on.jpg
where the prefix 'b' is to avoid leading '-' which may perturb UNIX commands (like
'rm'), and where 'xxxx' is created from the given informations by the formula:
xxxx = web_arg_encode(sha1((color,text,width)))
Hence, distinct informations give distinct file names.
define String // returns xxxx
create_button_images
(
String site_directory,
RGBA color,
String text,
Int32 width,
SystemFont font
) =
with xxxx = web_arg_encode(sha1((color,text,width))),
buttons_dir = site_directory+"/public/buttons",
off_filepath = buttons_dir+"/b"+xxxx+"_off.jpg",
on_filepath = buttons_dir+"/b"+xxxx+"_on.jpg",
if file_exists(on_filepath)
then xxxx
else with
text_width = printed_text_width(font,text),
button_width = max(width,text_width+12),
button_height = (Int32)20,
light_color = lighten(color,60),
very_light_color = lighten(light_color,30),
dark_color = darken(color,40),
background_off =
create_button_background(color,button_width,button_height),
background_on =
create_button_background(light_color,button_width,button_height),
draw_button_text(background_off,text,text_width,very_light_color,dark_color,font);
draw_button_text(background_on, text,text_width,very_light_color,dark_color,font);
forget(write_image_to_JPEG_file(to_JPEG(background_off),
off_filepath,
100));
forget(write_image_to_JPEG_file(to_JPEG(background_on),
on_filepath,
100));
xxxx.
*** [5.4] Formating an actioner.
An actioner works as follows. Assume first that it refers to a form. When it is clicked
upon, the actioner puts (via 'onMouseDown') the URL into the 'action' attribute of the
form, and submits the form, using the JavaScript command 'form_name.submit()'. If the
actioner does not refer to a form, it fires the URL directly via 'href', because in
that case, the actioner is always an tag.
The URL itself is composed using the connection sort (same, http or https), the common
name and port number (if needed), the state name, the action name, and the extra
operands, which are put into a query string. It may look like this:
http://common_name:port/?s=state_name&a=action_name&oname=value...
Each extra operand is a pair of strings: (name,value). It is formated as:
&oname=value
define String
format_extra_operands
(
List((String,String)) l
) =
if l is
{
[ ] then "",
[h . t] then if h is (n,v) then
"&o"+n+"="+v+format_extra_operands(t)
}.
In case the target is another window, we need to format the options for this window.
define String
format
(
List(Other_Window_Option) l
) =
if l is
{
[ ] then "",
[h . t] then if h is
{
resizable then "resizable",
scrollbars then "scrollbars",
width(w) then "width="+w,
height(h) then "height="+h
} + if t is [ ] then "" else (","+format(t))
}.
public define Printable_tree
format_choices
(
List(String) l
) =
if l is
{
[ ] then [ ],
[h . t] then ["",h . format_choices(t)]
}.
public define Printable_tree
format_choices
(
List(String) l,
String selected
) =
if l is
{
[ ] then [ ],
[h . t] then if h = selected
then [" ",h . format_choices(t)]
else [" ",h . format_choices(t,selected)]
}.
variable Int32 count = 0.
define Int32
new_count
=
count <- *count+1;
*count.
The next function composes the URL. It is a JavaScript URL when the target is another
window.
define String
make_actioner_url
(
CommonInfo cinfo,
Actioner_Connection connection,
Actioner_Target target,
String state_name,
String action_name,
List((String,String)) extra_ops,
Bool is_https
) =
if cinfo is info(common_name,http_port,https_port,site_directory,secret,font) then
with strict_url =
if connection is
{
same then "/",
http then "http://"+common_name+":"+http_port+"/",
https then "https://"+common_name+":"+https_port+"/",
} +
"?s=" + state_name + "&a=" + action_name +
format_extra_operands(extra_ops),
if target is
{
same then strict_url,
other(wn,ops) then
"javascript:void window.open('"+strict_url+"&t="+wn+"','"+wn+"','"+format(ops)+"')"
}.
Depending on the fact that the actioner refers to a form or not, the URL is used in two
different ways. If the actioner does not refer to a form, it is realized by a ''
tag, with a 'href' attribute. If it refers to a form, it is still realized by a ' '
tag, but with no href attribute. In this case, we use the 'onMouseDown' or 'onChange'
event handler. The handler calls a JavaScript function which puts the URL as the value
of the 'action' attribute of the form, and submits the form.
type URL_or_JavaScript:
url (String),
javascript (Printable_tree script, Printable_tree handler).
define URL_or_JavaScript
format_action
(
String the_url,
Maybe(String) mb_form_name
) =
if mb_form_name is
{
failure then
url(the_url),
success(form_name) then
with n = new_count,
javascript(
[
""
],
["pfu",form_name,n,"();"]
)
}.
Now, we format the actioner according to its aspect.
define String
format
(
List(Text_Option) l
).
define Printable_tree
format_actioner
(
CommonInfo cinfo,
String state_name,
Actioner_Connection connection,
Actioner_Target target,
Actioner_Aspect aspect,
String action_name,
List((String,String)) extra_ops,
Maybe(String) mb_form_name,
Bool is_https,
) =
if cinfo is info(common_name,http_port,https_port,site_dir,secret,font) then
with url = make_actioner_url(cinfo,connection,target,
state_name,action_name,extra_ops,is_https),
with action = format_action(url,mb_form_name),
if aspect is
{
link(opt,text) then [
if action is
{
url(u) then
[" "],
javascript(s,h) then
[s," "]
},
text,
" "
],
button(url_on,url_off) then
[ if action is
{
url(u) then ["",
" ",
" "
],
button(text,width,color) then
with xxxx = create_button_images(site_dir,
to_rgba(color),text,width,font),
url_on = "buttons/b"+xxxx+"_on.jpg",
url_off = "buttons/b"+xxxx+"_off.jpg",
[ if action is
{
url(u) then ["",
" ",
" "
],
immediate_selector(name,size,choices) then
[ if action is
{
url(u) then ["",
format_choices(choices)," "
]
}.
define Printable_tree
format_local_popup_button
(
CommonInfo cinfo,
Actioner_Aspect aspect,
Int32 n,
) =
if cinfo is info(common_name,http_port,https_port,site_dir,secret,font) then
[ "",
"",
if aspect is
{
link(opt,text) then [text],
button(url_off,url_on) then
[
" ",
],
button(text,width,color) then
with xxxx = create_button_images(site_dir,
to_rgba(color),text,width,font),
url_on = "buttons/b"+xxxx+"_on.jpg",
url_off = "buttons/b"+xxxx+"_off.jpg",
[
" ",
],
immediate_selector(name,size,choices) then alert
},
" "].
*** [5.5] Formating a private download link.
We get the absolute path of the file to be downloaded, and the name under which it
should appear to the client. The function 'format_private_download' creates an
hypertext link for downloading the file. The secured mecanism of private download is
used. This function is called by the function which formats HTML_Any($T) elements.
define Printable_tree
format_private_download
(
CommonInfo cinfo,
String sn, // state name
String abs_path, // absolute file path on server
String name, // name of file as it appears in the browser
String extra, // extra extension
Maybe((String,List((String,String)))) action
) =
if cinfo is info(common_name,http_port,https_port,site_directory,secret,font) then
with private_download_directory = site_directory+"/private_download",
with auth = make_authorization(site_directory,secret,abs_path),
[
"",
name,
" "
].
*** [5.6] Formating rows and cells in a table.
define Int32
percent
(
Int32 p
) =
if p < 0 then 0 else if p > 100 then 100 else p.
Formating cell options.
define String
format
(
BackgroundOption o
) =
if o is
{
repeat then "",
repeat_horizontal then "; background-repeat: repeat-x",
repeat_vertical then "; background-repeat: repeat-y",
no_repeat then "; background-repeat: no-repeat"
}.
define String
format
(
List(Cell_Option) options
) =
if options is
{
[ ] then "",
[h . t] then
if h is
{
left then " align=left",
h_center then " align=center",
right then " align=right",
top then " valign=top",
v_center then " valign=center",
bottom then " valign=bottom",
base_line then " valign=baseline",
background_color(c) then " bgcolor="+format(c),
background_image(n,o) then " style=\"background: url("+n+")"+format(o)+"\"",
width(w) then " width="+w,
percentage_width(n) then " width="+percent(n)+"%",
height(h) then " height="+h,
columns(n) then " colspan="+n,
rows(n) then " rowspan="+n,
nowrap then " nowrap"
}
+ format(t)
}.
Formating cells in a row.
define Printable_tree
format
(
List(HTML_Cell($T)) cells,
$T -> Printable_tree format_element
) =
if cells is
{
[ ] then [ ],
[h . t] then if h is cell(options,element) then
["",
format_element(element),
" "
. format(t,format_element)]
}.
Formating the rows in a table.
define Printable_tree
format
(
List(HTML_Row($T)) rows,
$T -> Printable_tree format_element,
) =
if rows is
{
[ ] then [ ],
[h . t] then if h is row(options,cells) then
["",
format(cells,format_element),
" "
. format(t,format_element)]
}.
define Printable_tree
format
(
List(TextAreaOption) l
) =
if l is
{
[] then [],
[h . t] then if h is
{
disabled then ["disabled " . format(t)]
}
}.
*** [5.7] Formating elements which may be put anywhere.
The function below involves the parameter $T which is later instantiated as
'HTML_In_Form' or as 'HTML_Off_Form'. Now, since there are dictinct 'format' functions
for these two types, and because formating of tables requires recursive calls of such
functions, it is necessary to provide the 'format' function to be called recursively as
an argument. Putting naively a call to 'format' will not work, because the compiler
will look for a function able to format data of type $T (which is at that time distinct
from any other type, including our two types). Such a function does not exist. Hence
the function to be called for formating elements must be passed as a functional
argument (called 'format_element' below). Actually, what we pass is a function taking
a unique argument of type $T. Other informations (like the name of the state) are
already in the function by way of full functionality.
Formating text options. They are formated in CSS syntax, to be used within a
'style=...'.
define String
format
(
List(Text_Option) l
) =
if l is
{
[ ] then "",
[h . t] then if h is
{
size(n) then "font-size:"+n+"pt",
font(fn) then "font-family:"+fn,
color(c) then if c is rgb(r,g,b) then
"color:rgb("+int8_to_int32(r)+","+int8_to_int32(g)+","+int8_to_int32(b)+")",
italic then "font-style:italic",
oblique then "font-style:oblique",
small_capitals then "font-variant:small-caps",
bold then "font-weight:bold",
underlined then "text-decoration:underline",
left_justified then "text-align:left",
right_justified then "text-align:right",
justified then "text-align:justify",
line_through then "text-decoration:line-through"
} + if t is [ ] then "" else ("; "+format(t))
}.
Formating table options.
define String
format
(
List(Table_Option) l,
Bool border_seen
) =
if l is
{
[ ] then if border_seen then "" else " border=0 cellspacing=0 cellpadding=0",
[h . t] then if h is
{
background_color(c) then " bgcolor="+format(c)+format(t,border_seen),
background_image(url) then " background="+url+format(t,border_seen),
border(o,top,i,c) then " border="+o+" cellspacing="+top+" cellpadding="+i+
" bordercolor="+format(c)+format(t,true),
width(w) then " width="+w+format(t,border_seen),
percentage_width(p) then " width="+percent(p)+"%"
}
}.
define Printable_tree
format_scroller
(
String sn,
Int32 width,
Int32 height,
Int32 content_width,
Int32 content_height,
Int32 idnum, // identifying the scroller
$T content,
$T -> Printable_tree format_element
) =
[
"",
"",
"",
"",
"",
"
",
format_element(content),
"
",
"
",
" ",
"",
"",
" ",
" ",
"
",
" ",
" ",
(if content_width > width then
[
"",
"",
"",
"",
" ",
" ",
" ",
"
",
" ",
" ",
] else [ ]),
"
"
].
define Printable_tree
popup_topbar
(
CommonInfo cinfo,
String title,
RGB color,
Int32 width,
) =
if cinfo is info(common_name,http_port,https_port,site_directory,secret,font) then
with xxxx = web_arg_encode(sha1((title,color,width))),
path = site_directory+"/public/buttons/t"+xxxx+".jpg",
result = (Printable_tree)[" "],
if file_exists(path) then result else
with col = to_rgba(color),
bg = create_button_background(col,width,20),
very_light_color = lighten(col,70),
dark_color = darken(col,40),
title_width = printed_text_width(font,title),
draw_button_text(bg,title,title_width,very_light_color,dark_color,font);
forget(write_image_to_JPEG_file(to_JPEG(bg),path,100));
result.
define Printable_tree
popup_close_button
(
CommonInfo cinfo,
RGB color,
String div_name,
String state_var_name,
) =
if cinfo is info(common_name,http_port,https_port,site_directory,secret,font) then
with xxxx = web_arg_encode(sha1(color)),
path_on = site_directory+"/public/buttons/c"+xxxx+"_on.jpg",
path_off = site_directory+"/public/buttons/c"+xxxx+"_off.jpg",
result = (Printable_tree)[" "],
if file_exists(path_on) then result else
with col = to_rgba(color),
title = "x",
title_width = printed_text_width(font,title),
bg_on = create_button_background(lighten(col,30),20,20),
bg_off = create_button_background(col,20,20),
very_light_color = lighten(col,70),
dark_color = darken(col,40),
draw_button_text(bg_on,title,title_width,very_light_color,dark_color,font);
draw_button_text(bg_off,title,title_width,very_light_color,dark_color,font);
forget(write_image_to_JPEG_file(to_JPEG(bg_on),path_on,100));
forget(write_image_to_JPEG_file(to_JPEG(bg_off),path_off,100));
result.
The function below formats a datum of type 'HTML_Any($T)'.
define Printable_tree
format
(
CommonInfo cinfo,
String sn, // state_name
Var(Int32) ic_v,
HTML_Any($T) element,
$T -> Printable_tree format_element, // able to format a datum of type $T
Bool is_https,
) =
if cinfo is info(common_name,http_port,https_port,site_directory,secret,font) then
if element is
{
any_text(opts,t) then
["",t," "],
any_preformated(opts,s) then
["",s," "],
//["",s," "],
any_paragraph(opts,t) then
["",t,"
"],
any_image(url) then
[" "],
any_table(opts,rows) then
["",format(rows,format_element),"
"],
any_center(e) then
["",format_element(e)," "],
any_mail_to(email,elem) then
["",format_element(elem)," "],
any_scroller(w,h,cw,ch,c) then
format_scroller(sn,w,h,cw,ch,new_idnum(ic_v),c,format_element),
any_fixed_size(w,h,c) then
with url = create_secondary_document(site_directory,secret,sn,format_element,c,w),
["",
"secondary document",
" "],
any_fixed_size(w,h,fn) then
with url = fn+"?zauth="+make_authorization(site_directory,secret,
fn),
["",
"secondary document",
" "],
any_actioner(c,t,a,an,eo,fn) then
format_actioner(cinfo,sn,c,t,a,an,eo,fn,is_https),
any_local_popup(a,c,x,y,t,clr,w) then
with n = new_idnum(ic_v),
b = format_local_popup_button(cinfo,a,n),
f = ["",
"
",
popup_topbar(cinfo,t,clr,w),
" ",
popup_close_button(cinfo,clr,"lpu_"+n,"lpust_"+n),
" ",
"",
format_element(c),
"
",
"
"],
[ ""
],
any_foreign_link(url,name) then
["",name," "],
any_private_download(url,name,extra_ext,action) then
format_private_download(cinfo,sn,url,name,extra_ext,action)
}.
*** [5.8] Formating 'in form' elements.
define Printable_tree
format
(
CommonInfo cinfo,
String fn, // form_name
String sn, // state_name
Var(Int32) ic_v, // idnum counter variable
HTML_In_Form element,
Bool is_https,
) =
if cinfo is info(common_name,http_port,https_port,site_directory,secret,font) then
with format_element = (HTML_In_Form e) |-> format(cinfo,fn,sn,ic_v,e,is_https),
if element is
{
literal(t) then t,
sequence(l) then flat(map(format_element,l))
text(opts,t) then
format(cinfo,sn,ic_v,any_text(opts,t),format_element,is_https),
preformated(o,s) then
format(cinfo,sn,ic_v,any_preformated(o,s),format_element,is_https),
paragraph(opts,t) then
format(cinfo,sn,ic_v,any_paragraph(opts,t),format_element,is_https),
image(url) then
format(cinfo,sn,ic_v,any_image(url),format_element,is_https),
table(opts,rows) then
format(cinfo,sn,ic_v,any_table(opts,rows),format_element,is_https),
center(e) then
format(cinfo,sn,ic_v,any_center(e),format_element,is_https),
mail_to(a,e) then
format(cinfo,sn,ic_v,any_mail_to(a,e),format_element,is_https),
scroller(w,h,cw,ch,c) then
format(cinfo,sn,ic_v,any_scroller(w,h,cw,ch,c),format_element,is_https),
actioner(c,t,a,an,eo) then
format(cinfo,sn,ic_v,any_actioner(c,t,a,an,eo,success(fn)),format_element,is_https),
local_popup(a,c,x,y,t,clr,w) then
format(cinfo,sn,ic_v,any_local_popup(a,c,x,y,t,clr,w),format_element,is_https),
foreign_link(url,name) then
format(cinfo,sn,ic_v,any_foreign_link(url,name),format_element,is_https),
private_download(url,name,extra,action) then
format(cinfo,sn,ic_v,any_private_download(url,name,extra,action),format_element,is_https),
text_input(n,i,w) then
[" "],
//[" "],
password_input(n,w) then
[" "],
//[" "],
text_area(opts,n,i,w,h) then
[""],
file_upload(n,w) then
[" "],
selector(n,s,cs) then
["",format_choices(cs)," "],
selector(n,s,cs,sd) then
["",format_choices(cs,sd)," "],
radio_button(n,v,c) then
[" "],
check_box(n,c) then
[" "]
}.
*** [5.9] Formating 'off form' elements.
The encryption type 'multipart/form-data' is required for a form containing an upload.
define Bool
contains_an_upload
(
HTML_In_Form form_content
).
define Bool
contains_an_upload
(
HTML_Row(HTML_In_Form) row
) =
mapor(contains_an_upload,
map(content,cells(row))).
define Bool
contains_an_upload
(
HTML_In_Form form_content
) =
if form_content is
{
literal(t) then false,
sequence(l) then mapor(contains_an_upload,l)
text(o,t) then false,
preformated(o,s) then false,
paragraph(o,t) then false,
image(u) then false,
table(o,rows) then mapor(contains_an_upload,rows),
center(e) then contains_an_upload(e),
mail_to(m,e) then false, // 'e' may but should not contain an upload
scroller(w,h,cw,ch,e) then contains_an_upload(e),
actioner(c,t,a,an,eo) then false,
local_popup(a,c,x,y,t,clr,w) then contains_an_upload(c),
foreign_link(u,n) then false,
private_download(p,n,e,a) then false,
text_input(n,i,w) then false,
password_input(n,w) then false,
text_area(o,n,i,w,h) then false,
file_upload(n,w) then true,
selector(n,s,c) then false,
selector(n,s,c,p) then false,
radio_button(n,v,c) then false,
check_box(n,c) then false
}.
define String
enctype
(
HTML_In_Form form_content
) =
if contains_an_upload(form_content)
then " enctype=multipart/form-data"
else "".
define Printable_tree
format
(
CommonInfo cinfo,
String sn, // state_name
Var(Int32) ic_v, // 'idnum' counter variable
HTML_Off_Form element,
Bool is_https,
) =
if cinfo is info(common_name,http_port,https_port,site_directory,secret,font) then
with format_element = (HTML_Off_Form e) |-> format(cinfo,sn,ic_v,e,is_https),
if element is
{
literal(t) then t,
sequence(l) then flat(map(format_element,l)),
text(opts,t) then
format(cinfo,sn,ic_v,any_text(opts,t),format_element,is_https),
preformated(o,s) then
format(cinfo,sn,ic_v,any_preformated(o,s),format_element,is_https),
paragraph(opts,t) then
format(cinfo,sn,ic_v,any_paragraph(opts,t),format_element,is_https),
image(url) then
format(cinfo,sn,ic_v,any_image(url),format_element,is_https),
table(opts,rows) then
format(cinfo,sn,ic_v,any_table(opts,rows),format_element,is_https),
center(e) then
format(cinfo,sn,ic_v,any_center(e),format_element,is_https),
mail_to(a,e) then
format(cinfo,sn,ic_v,any_mail_to(a,e),format_element,is_https),
scroller(w,h,cw,ch,c) then
format(cinfo,sn,ic_v,any_scroller(w,h,cw,ch,c),format_element,is_https),
fixed_size(w,h,c) then
format(cinfo,sn,ic_v,any_fixed_size(w,h,c),format_element,is_https),
fixed_size(w,h,fn) then
format(cinfo,sn,ic_v,any_fixed_size(w,h,fn),format_element,is_https),
actioner(c,t,a,an,eo) then
format(cinfo,sn,ic_v,any_actioner(c,t,a,an,eo,failure),format_element,is_https),
actioner(c,t,a,an,eo,fn) then
format(cinfo,sn,ic_v,any_actioner(c,t,a,an,eo,success(fn)),format_element,is_https),
local_popup(a,c,x,y,t,clr,w) then
format(cinfo,sn,ic_v,any_local_popup(a,c,x,y,t,clr,w),format_element,is_https),
foreign_link(url,name) then
format(cinfo,sn,ic_v,any_foreign_link(url,name),format_element,is_https),
private_download(url,name,extra,action) then
format(cinfo,sn,ic_v,any_private_download(url,name,extra,action),format_element,is_https),
form(fn,c) then
[
""
]
}.
*** [5.10] Formating meta-tags.
define Printable_tree
format_keywords
(
List(String) l
) =
if l is
{
[] then [ ],
[h . t] then if t is []
then [h]
else [h , ", " . format_keywords(t)]
}.
define Printable_tree
format
(
CommonInfo cinfo,
String state_name,
HTML_Meta m,
Bool is_https
) =
if m is
{
keywords(l) then [" "],
refresh(co,ta,an,delay) then
[" "],
meta(n,c) then [" "],
http_equiv(n,c) then [" "],
generic_meta(l) then [" if p is (n,v) then [n,"=\"",v,"\" "],
l)),
">"]
}.
define Printable_tree
format
(
CommonInfo cinfo,
String state_name,
List(HTML_Meta) metas,
Bool is_https
) =
if metas is
{
[] then [],
[h . t] then [format(cinfo,state_name,h,is_https)
. format(cinfo,state_name,t,is_https)]
}.
define Printable_tree
format
(
Body_Option o
) =
if o is
{
background_color(c) then [" bgcolor=" , (String)format(c)],
background_image(n) then [" background=", n]
}.
define Printable_tree
format
(
List(Body_Option) l
) =
if l is
{
[ ] then [ ],
[h . t] then [format(h) . format(t)]
}.
define Printable_tree
format
(
CommonInfo cinfo,
String state_name,
HTML_Page page,
Bool is_https,
) =
if cinfo is info(common_name,http_port,https_port,site_directory,secret,font) then
with ic_v = var((Int32)0),
if page is
{
html_page(title,metas,body) then
if body is body(options,element) then
[
"",
"",
"",
" ",
"",
"",title," ", // put title
format(cinfo,state_name,metas,is_https), // format the metas
"",
"", // format body options
//"",
format(cinfo,state_name,ic_v,element,is_https),
//" ",
"",
""
]
}.