HTML Templates for Lisp by Gene Michael Stover Listing One <lisp>*title*</lisp> ;; Generate HTML and collect it into a string, ;; then return the string. (with-output-to-string (strm) (dolist (n n-lst) (format strm "~&" n (expt n 2))))
nn2
~A~A
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 " ") (send (progn (with-output-to-string (strm) (dolist (n n-lst) (format strm "~&" n (expt n 2)))))) (send "
nn2
~A~A
")) 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 (dolist (n ht) (format strm "~&~@{~}" n (log n) (expt n 2)))
N Log N N2
~A
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