Example using XSLT in a Hunchentoot handler to deliver an HTML page
Suppose you're writing a web application (say, using Hunchentoot), and you would like to show directory contents.
We do that in two steps, strictly separating the programmer-written Lisp code from the XSLT stylesheet that a web designer might want to tweak afterwards:
- First we call CL:DIRECTORY, and build a small in-memory XML document listing the files.
- In the main step, we run that XML document through an XSL stylesheet to generate HTML.
The example requires hunchentoot and xuriella:
(asdf:operate 'asdf:load-op :hunchentoot) (asdf:operate 'asdf:load-op :xuriella)
Let's start hunchentoot and register a handler for the example first:
(push (tbnl:create-prefix-dispatcher "/show-directory" 'show-directory) tbnl:*dispatch-table*) (tbnl:start-server :port 4242)
Since we might want to write many different handlers using stylesheets, we factor the APPLY-STYLESHEET call out into a convenient macro WITH-STYLESHEET. Its body is expected to provide XML, which it will send through the stylesheet and return the result as a string.
Note the use of WITH-XML-OUTPUT and STP:MAKE-BUILDER to build the intermediate XML as an in-memory document using STP.
(In real-world code, we could optimize this a little by compiling the stylesheet ahead of time using PARSE-STYLESHEET, and building a cache out of stylesheet objects in a hash table somewhere.)
(defmacro with-stylesheet ((stylesheet-pathname) &body body) `(invoke-with-stylesheet (lambda () ,@body) ,stylesheet-pathname)) (defun invoke-with-stylesheet (fn stylesheet-pathname) (xuriella:apply-stylesheet (pathname stylesheet-pathname) (cxml:with-xml-output (stp:make-builder) (funcall fn))))
Building the temporary XML
Now for the handler calling DIRECTORY. We want our XML to look like this:
<directory namestring="/home/jrhacker/"> <file>hello-world.lisp</file> <file>mbox</file> ... </directory>which we can generate easily using WITH-ELEMENT and DOLIST:
(defun show-directory () (with-stylesheet ("directory.xsl") (cxml:with-element "directory" (let ((directory (user-homedir-pathname))) (cxml:attribute "namestring" (namestring directory)) (dolist (file (directory (merge-pathnames "*.*" directory))) (cxml:with-element "file" (cxml:text (enough-namestring file directory))))))))
An XSL stylesheet as a template
Finally, the XSL stylesheet that turns this into HTML for us. Note the xsl:version on the root element, which marks the literal result element used as a stylesheet.
Since <html> is the root element, the stylesheet processor will turn on its HTML output method automatically, and generate HTML 4 rather than XML. (Powered by Closure HTML.)
To keep the example short and simple, our HTML is not very fancy.
<html xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xsl:version="1.0"> <head> <title> <xsl:value-of select="/directory/@namestring"/> </title> </head> <body> <h1> Index of <xsl:value-of select="/directory/@namestring"/> </h1> <ul> <xsl:for-each select="/directory/file"> <li> <xsl:value-of select="."/> </li> </xsl:for-each> </ul> </body> </html>
That's it. If you open http://localhost:4242/show-directory in a browser, you should see a listing of your home directory.