Using with-lhtml-string

<-Previous | ^UP^ | Next->

LHTML is a Markup Language included in Gendl (standing for Lisp HTML) which provides a convenient way to generate html using lisp-like expressions. It is intended to be portable and shoud work with all ANSI standard Common Lisp Implementations.

The main element of lhtml is the macro with-lhtml-string, which transforms its body into a html string

In general, we use with-lhtml-string when creating html strings for use in GWL web pages

with-lhtml-string takes an optional input :indent. By default it is nil which results in html with no unnecessary whitespace to minimise the bandwith being used. But for debugging purposes, the resulting html may be difficult to read. Setting :indent to t causes line breaks to be inserted and nested tags properly indented

GWL-USER  (with-lhtml-string ()(:table (:tr (:td))))"<table><tr><td></td></tr></table>"GWL-USER  (with-lhtml-string (:indent t)(:table (:tr (:td))))"<table>  <tr>    <td></td>  </tr></table>"


To generate html we use a nested list of s-expressions. Each list beginning with a keyword is transformed into a html tag of the same name by the following rules:

  • If the list contains nothing but a keyword an empty element is written
    GWL-USER  (with-lhtml-string()(:br))<br />
  • If the keyword is followed by another keyword, it's interpreted as an attribute and the next form is the value of that attribute
    GWL-USER  (with-lhtml-string()(:span :class "myclass"))"<span class="myclass"></span>"
  • Multiple attribute-value pairs may be specified
    GWL-USER  (with-lhtml-string()(:table :border 1 :cellspacing 3 :cell-padding 5))"<table border="1" cellspacing="3" cell-padding="5"></table>"
  • The first form which isn't a keyword and follows either the tag or an attribute value is interpreted as the tag content
    GWL-USER  (with-lhtml-string()(:p "This is content"))"<p>This is content</p>"GWL-USER  (with-lhtml-string()(:span :class "myclass" "This is more content"))"<span class="myclass">This is more content</span>"
  • To make it slightly easier to read, the tag and all attribute-value pairs may be enclosed in an additional list, but this is purely optional
    GWL-USER  (with-lhtml-string()(:span :class "myclass" "This is more content"))"<span class="myclass">This is more content</span>"GWL-USER  (with-lhtml-string()((:span :class "myclass") "This is more content")))"<span class="myclass">This is more content</span>"
  • Tags may be embedded in other tags
    GWL-USER  (with-lhtml-string()(:table :border 1(:tr(:td "Cell 1")(:td "Cell 2"))))"<table border="1"><tr><td>Cell 1</td><td>Cell 2</td></tr></table>"

Markup with computed content

So far any tag content we have shown is static, so how do we handle computed content? If we just wish to output the value of a slot, we must wrap that slot with the macro str

GWL-USER  (let ((cell-1 "Cell 1 content")(cell-2 "Cell 2 content"))(with-lhtml-string ()(:table (:tr (:td (str cell-1)) (:td (str cell-2))))))"<table><tr><td>Cell 1 content</td><td>Cell 2 content</td></tr></table>"

If we want to present the value of a slot with some formatting, rather than use the format function to format the value of the slot, we could wrap the value in the macro fmt, which takes the same directives as the formatfunction

GWL-USER  (let ((cell-1 1)(cell-2 2))(with-lhtml--string ()(:table (:tr (:td (fmt "Cell ~a content" cell-1)) (:td (fmt "Cell ~a content" cell-2))))))"<table><tr><td>Cell 1 content</td><td>Cell 2 content</td></tr></table>"

It is also possible to embed any type of Lisp processing within the body of with-lhtml-string, although whenever we break out of the html generation to do some processing, once we restart html generation again we need to wrap the html generation in the htm macro

GWL-USER  (with-lhtml-string ()(:table (:tr (let ((lis (list 1 2)))(dolist (a lis)(htm (:td (fmt "Cell ~a" a))))))))"<table><tr><td>Cell 1</td><td>Cell 2</td></tr></table>"

Finally, a word of warning. lhtml knows nothing about html, it just processes what it is given according to the rules above. So there is no checking to see if the first keyword is a valid html tag, or if the second keyword is a valid tag attribute. Thats your job!