This document copyright (c) 2000-2003 Franz Inc.
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)) (incf *my-counter*) ; evaluated but not printed )))
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) [Macro]
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:
(html-stream stream form1 form2 ... formn) [Macro]
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 (: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:
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 (: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:
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) (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
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:
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) (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)
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
Examples of valid lhtml:
(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.