Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
414 lines (340 sloc) 14.9 KB

HTML Generation Facility

Copyright (c) Franz Inc.

Introduction

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.

Example

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.

html macros

Now that you have a sense of how the html macro works, we will describe the syntax in detail.


net.html.generator:html [macro]

(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 the html 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 the princ 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 the prin1 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 put bgcolor="\#00ff00" in the argument if the expression (frob) returns true at runtime.

net.html.generator:html-stream [macro]

(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.

Examples

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

HTML generation functions

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 containing lhtml 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 the html 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").

net.html.generator:html-print

(html-print lhtml stream)

Print the Lisp HTML expression lhtml to the stream.


net.html.generator:html-print-list

(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.

You can’t perform that action at this time.