HTML Templates for Lisp
by Gene Michael Stover
Listing One
*title*
| n | n2 |
;; Generate HTML and collect it into a string,
;; then return the string.
(with-output-to-string (strm)
(dolist (n n-lst)
(format strm "~&| ~A | ~A |
"
n (expt n 2))))
Listing Two
;; Example of using HTML templates
;; Dynamically transform the template into a function & call it.
(call-template "potato.tmpl")
;; Transform the template into a function in-place
(let ((*i* 0))
(inplace-template "potato.tmpl")
;; The template could use & alter the value of I.
(format t "*I* is ~A" *i*))
;; Load the template into a function at compile-time
(def-template-fun potato (arg0 arg1 arg2) "potato.tmpl")
;; then call it as a regular Lisp function
(potato 1 2 3)
Listing Three
;; Example of what READ-TEMPLATE might return
((send "
")
(send (progn *title*))
(send "
| n | n2 |
")
(send (progn (with-output-to-string (strm)
(dolist (n n-lst)
(format strm "~&| ~A | ~A |
"
n (expt n 2))))))
(send "
"))
Listing Four
(defun read-template (template)
"Returns a Lisp datum derived from the HTML template, which is a string."
(mappend #'(lambda (lst2)
(list `(send ,(first lst2) strm)
(read-from-string
(format nil "(send (progn~%~A~%) strm)"
(second lst2)))))
(split-template-into-pairs template)))
Listing Five
(defun call-template (pn &optional (strm *standard-output*) ht)
"Given the pathname of an HTML template file, convert the file to a Lisp
function & call it. STRM is the destination of the template's HTML output.
HT is an extra argument for the Lisp code in the template. The idea is that
HT is a hash table."
(funcall (load-template pn) strm ht))
(defun load-template (pn)
"Convert the HTML template to a function & return the closure. The
function is defined at top level."
(eval `#'(lambda (&optional (strm *standard-output*) ht)
,@(read-template (slurp-file pn)))))
Listing Six
;; Program to write two HTML files containing some tabular data.
(defun example (n-lst pn)
(with-open-file (strm pn :direction :output)
(call-template "listing-07.tmpl" strm n-lst)))
(example "units.html" '(1 2 3))
(example "tens.html" '(10 20 30))
Listing Seven
table of numbers
| N | Log N | N2 |
(dolist (n ht)
(format strm "~&~@{| ~A | ~}
"
n (log n) (expt n 2)))
Listing Eight
(defmacro inplace-template (pn &optional (strm *standard-output*))
`(funcall #'(lambda (strm) ,@(read-template (slurp-file pn)))
,strm))
Listing Nine
;; Use INPLACE-TEMPLATE so an HTML template function can
;; access local variables. If the HTML template file holds this:
;; X is x.
;; then
(let ((x 42))
;; this template function will show 42,
(inplace-template "the-template.tmpl"))
;; but this template function will FAIL because X is not in scope.
(inplace-template "the-template.tmpl")
Listing Ten
(defmacro def-template-fun (name args pn)
"From the HTML template file PN create a function called NAME. ARGS should
contain a STRM formal argument. It could contain other formal arguments, too.
(A better implementation of this function would insert the STRM argument if
it was missing.)"
`(defun ,name ,args
,(format nil "Created from the HTML template ~S." pn)
,@(read-template (slurp-file pn))
',name))
Listing Eleven
(defun def-all-templates (&optional (dir *def-all-templates-default*))
(eval `(progn
,@(mapcar #'(lambda (pn)
`(def-template-fun
,(template-name-from-pathname pn)
(&optional (strm *standard-output*) (ht nil))
,pn))
(directory dir)))))
Listing Twelve
#! /usr/local/bin/clisp
;; An example CGI program
(format t "Content-type: text/html~%~%")
;; Load libraries & such, including the HTML template library.
(setq *load-verbose* nil)
(load "all-that-template-stuff.lisp")
;; Convert all the HTML template files into named Lisp functions. Assume
;; the templates are called MYINSERT, MYDELETE, MYEDIT, and MYERROR.
(def-all-templates (make-pathname :directory '(:relative "templates")
:name :wild :type "tmpl"))
;; a very simple "main"
(let ((action (get-cgi-arg "action")))
(cond ((equal action "insert") (myinsert))
((equal action "delete") (mydelete))
((equal action "edit") (myedit))
(t (myerror))))
4