We've defined a pair of macros which enable a program to generate HTML in a concise and readable manner. These macros are quite a bit different than those found in other HTML generation systems and thus we'll briefly describe the justification for this design.
HTML is a concise markup language. There is a tendency in language design to
assume that one's users won't be as smart as one's self and thus one tends to
dumb things down. For example, HTML uses <p>
to start a paragraph. The
language designer says to himself: "while I know that p
means paragraph, my
users won't so I'll spell it out as with-paragraph
". A similar thing is
done for all the other HTML commands and soon a program to generate HTML
contains so many long markup commands that it's hard to see the text of the
document from the markup commands. A second problem is that as a user you're not
sure exactly what HTML will be generated by some high level with-xxx
forms. If you're trying to generate a particular sequence of HTML you're
left experimenting with the high level forms and hoping that by luck you get the
output you want. A third problem is that with the high level forms you're forced
to learn yet another markup language. There are plenty of books and reference
guides to HTML itself and it's an easy language to master. Learning a
particular high-level mapping of HTML is an added burden.
With our HTML generation macros you write the actual HTML and we just eliminate some of the tedious parts, such as closing off markup commands. The result is that the structure of the document is visible and you can use any book on HTML as a reference.
The following example of generated web page will be useful to refer to in the
discussion below of the syntax of the html
macro.
(defvar *my-counter* 0) ; initialize counter variable.
(html
(:html
(:head (:title "My Document"))
(:body (:h1 "My Document")
"Hello AllegroServe, the time is "
(:prin1 (get-universal-time))
;; evaluated but not printed
(incf *my-counter*))))
This particular example generates a complete web page, but it's possible to use
the html
macro to generate partial pages as well. In this example, the
generated page is surrounded by <html>
and </html>
due to the :html
form. The page contains a header and a body surrounded by their respective HTML
markers. The body of the document contains a level 1 header followed by the text
beginning "Hello, AllegroServe". Following that is printed the universal time at
the time that the page is generated (i.e now rather than when the macro was
first processed by lisp). The following incf
expression is evaluated but the
result is not printed. In this case we're keeping a private count of the number
of times this page has been accessed.
Now that you have a sense of how the html
macro works, we will describe the
syntax in detail.
(html form1 form2 ... formn)
The forms are processed from left to right. The most likely effect is that HTML
output is generated. The output is sent to the stream
net.html.generator:*html-stream*
. The html
macro is designed to run
inside AllegroServe's with-http-body
macro which binds *html-stream*
to the correct stream. Also the html-stream
macro described below binds
*html-stream*
before calling html
. The action taken by html
depends on what the form looks like at macro-expansion time. The possibilities
are:
- string - A string is simply written (using
princ
) to the output stream. Thus the string could contain embedded HTML commands. - keyword symbol - The keyword must name a known HTML operator. The result is
that the associated HTML markup command is sent to the output stream. The
mapping of keyword to HTML command is trivial - the print name of the
keyword is the HTML command. So
:p
emits<p>
. - list beginning with a keyword symbol - This names an
html
operator that may or may not have an associated inverse operator beginning with "/". The typical result of this form is to emit the associated HTML markup command, then process the items in the list in the same way as the forms are processed, and then emit the inverse markup command. Thus(:i "foo")
emits<i>foo</i>
. There is a special case when a single element list is given (see below for details). Also there are some special keywords that are commands to thehtml
macro rather than markup commands. They are described below. - list beginning with a list beginning with a keyword symbol - This is used to
specify markup commands that have parameters. For example
((:a href "/foo/bar.html") "the link")
turns into<a href="/foo/bar.html">the link</a>
. The arguments are in plist form: a sequence of names and values. The names are not evaluated, they should be symbols or strings. We often use keyword symbols for the names since that looks more lisp-like and reduces the number of symbols we create. The values are evaluated and printed with a function that escapes characters with special meaning in HTML :<
,>
,&
,"
. If the value is a symbol with a zero length print name, then something special is done: The name alone is printed without a following equal sign. For example:((:option :size 4 :selected '||) "foo")
generates<option size="4" selected>foo</option>
. This form of valueless argument is illegal HTML but in some older browsers it's the required syntax. - anything else - everything else is simply evaluated in the normal lisp way and the value thrown away.
Special cases:
(:princ arg1 arg2 .. argn)
causes the result of evaluating each of the args to be printed to the HTML stream using theprinc
function (which prints without inserting escape characters needed by the lisp reader to read the result).(:prin1 arg1 arg2 ... argn)
causes the result of evaluating each of the args to be printed to the HTML stream using theprin1
function (which prints by inserting escape characters needed by the lisp reader to read the result).(:princ-safe arg1 arg2 .. argn)
acts like the:princ
case except that the output is scanned for characters that could be considered HTML markup commands, and if found, these characters are escaped to prevent them from being treated as HTML markup commands.(:prin1-safe arg1 arg2 .. argn)
acts like the:prin1
case except that the output is scanned for characters that could be considered HTML markup commands, and if found, these characters are escaped to prevent them from being treated as HTML markup commands.:newline
simply inserts a newline into the HTML output stream. This will not have an effect on the result as viewed by a web browser (unless it is emitted while inside an HTML markup command that specifies preformatted input). The main use for this is to make the resulting HTML file easier to read by a human.- You can conditionally specify arguments to a markup command using an
argument name of
:if*
. Following the:if*
is a lisp expression which if true at runtime will cause the following argument value pair to be included in the argument tag. For example((:td :if* (frob) :bgcolor "\#00ff00") "xx")
will only putbgcolor="\#00ff00"
in the argument if the expression(frob)
returns true at runtime.
(html-stream stream form1 form2 ... formn)
This binds net.html.generator:*html-stream*
to the value of the stream
argument and then evaluates the form*
arguments just like the html
macro.
We will show how to build a page containing a table using successively more runtime customization of the table. First we show how to build a table of squares.
(defun simple-table-a ()
(with-open-file (p "test.html"
:direction :output
:if-exists :supersede
:if-does-not-exist :create)
(html-stream p
(:html
(:head (:title "Test Table"))
(:body
(:table
(:tr (:th "A") (:th "B"))
(:tr (:td "0") (:td "0"))
(:tr (:td "1") (:td "1"))
(:tr (:td "2") (:td "4"))
(:tr (:td "3") (:td "9"))
(:tr (:td "4") (:td "16"))
(:tr (:td "5") (:td "25"))))))))
The function simple-table-a
builds a page containing this table:
A | B |
---|---|
0 | 0 |
1 | 1 |
2 | 4 |
3 | 9 |
4 | 16 |
5 | 25 |
It isn't very pretty but it's easy to see the correspondence between the
html
macro and the resulting table. Note that if we had done, for example,
(:td 1)
instead of (:td "1")
then nothing would have been emitted. Only
constant strings are printed, not constant integers. To use an integer here we
would have had to do (:td (:princ 1))
.
We can use the ability to pass arguments to HTML markup commands to specify a border around the elements of the table as shown here:
(defun simple-table-b ()
(with-open-file (p "test.html"
:direction :output
:if-exists :supersede)
(html-stream p
(:html
(:head (:title "Test Table"))
(:body
((:table border 2)
(:tr (:th "A") (:th "B"))
(:tr (:td "0") (:td "0"))
(:tr (:td "1") (:td "1"))
(:tr (:td "2") (:td "4"))
(:tr (:td "3") (:td "9"))
(:tr (:td "4") (:td "16"))
(:tr (:td "5") (:td "25"))))))))
The resulting table is:
A | B |
---|---|
0 | 0 |
1 | 1 |
2 | 4 |
3 | 9 |
4 | 16 |
5 | 25 |
Suppose we wanted to make the table include the squares of numbers from zero to 100. That would take a lot of typing. Instead, let's modify the table generation function to compute a table of any size:
(defun simple-table-c (count)
(with-open-file (p "test.html"
:direction :output
:if-exists :supersede)
(html-stream p
(:html
(:head (:title "Test Table"))
(:body
((:table border 2)
(:tr (:th "A") (:th "B"))
(dotimes (i count)
(html (:tr (:td (:princ i))
(:td (:princ (* i i))))))))))))
Note that we can freely imbed calls to the html
macro within another call.
The dotimes
call inside the :body
expression is simply evaluated and its
value ignored. However the side effect of the dotimes
is to generate more
HTML and to send it to the stream bound in the html-stream
call. The result
of (simple-table-c 8)
is
A | B |
---|---|
0 | 0 |
1 | 1 |
2 | 4 |
3 | 9 |
4 | 16 |
5 | 25 |
6 | 36 |
7 | 49 |
We can specify at runtime values for the arguments to HTML markup forms. This function allows us to specify parameters of the table being built:
(defun simple-table-d (count border-width backg-color border-color)
(with-open-file (p "test.html"
:direction :output
:if-exists :supersede)
(html-stream p
(:html
(:head (:title "Test Table"))
(:body
((:table border border-width
bordercolor border-color
bgcolor backg-color
cellpadding 3)
(:tr ((:td bgcolor "blue")
((:font :color "white" :size "+1")
"Value"))
((:td bgcolor "blue")
((:font :color "white" :size "+1")
"Square")))
(dotimes (i count)
(html (:tr (:td (:princ i))
(:td (:princ (* i i))))))))))))
This demonstrates that in an HTML markup command argument list the keywords aren't evaluated but the values are. If we evaluate this expression:
(simple-table-d 10 3 "silver" "blue")
then we generate this table:
Value | Square |
---|---|
0 | 0 |
1 | 1 |
2 | 4 |
3 | 9 |
4 | 16 |
5 | 25 |
6 | 36 |
7 | 49 |
8 | 64 |
9 | 81 |
An example of conditional arguments to a markup command is this:
(defun simple-table-e (count)
(with-open-file (p "test.html"
:direction :output
:if-exists :supersede)
(html-stream p
(:html
(:head (:title "Test Table"))
(:body
((:table border 2)
(:tr (:th "A") (:th "B") (:th "C") (:th "D") (:th "E") (:th "F"))
(dotimes (i count)
(html (:tr
(dotimes (j count)
(html ((:td :if* (evenp j) :bgcolor "red"
:if* (not (evenp j)):bgcolor "green")
(:princ (* i j))))))))))))))
This sets the color of the columns to alternately red and green: Here
is (simple-table-e 6)
:
A | B | C | D | E | F |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 |
0 | 1 | 2 | 3 | 4 | 5 |
0 | 2 | 4 | 6 | 8 | 10 |
0 | 3 | 6 | 9 | 12 | 15 |
0 | 4 | 8 | 12 | 16 | 20 |
0 | 5 | 10 | 15 | 20 | 25 |
It's possible to express HTML using Lisp data structures. The form is based on
how HTML is written using the html
macro above.
Lisp HTML (lhtml
) is defined as one of the following
- a string, which is rendered as HTML by simply printing it. Thus the string can contain embedded HTML commands.
- a list beginning with a valid
lhtml
keyword and containinglhtml
forms. The valid keywords are those corresponding to the HTML entity tags, plus the special tags:princ
,:princ-safe
,:prin1
,:prin1-safe
,:newline
and:comment
. These act just as they do in thehtml
macro. This form is rendered as an opening tag, then the rendering of the body, and a closing HTML tag if one exists. - a list beginning with a list beginning with an
lhtml
keyword. This is the form used when attributes are to be supplied with the opening entity tag.
Examples of valid lhtml
:
"foo<i>bar</i>baz"
;(:i "foo")
;((:body :bgcolor "\#xffffff") "the body")
.
(html-print lhtml stream)
Print the Lisp HTML expression lhtml
to the stream
.
(html-print-list lhtml-list stream)
Print the list of lhtml
forms to the stream
. This is equivalent to
calling html-print
on every element of lhtml-list
.