Practical HTML generation in Racket servlets with txexpr
You want to generate a simple piece of HTML for your Racket web app, but the existing options don’t seem to quite cut it, or behave curiously.
Why is generating HTML in Racket like pulling teeth? Do we sharpen our razor and hunker down for some some custom HTML yak shaving?
A common (though not the only) approach to working with HTML in the context of Racket web servlets is to use X-expressions AKA xexprs. The
X-expression is for
XML. Even if you’re well aware of the differences between XML and HTML and are sensitive to the different needs that they address, from a structural point of view, the two are so similar that it would be a shame to throw out xexprs and decide that you need to roll your own HTML generator.
If you’re in the xexpr camp and want to know how to generate HTML, take a moment to learn about txexpr and, especially, txexpr’s
xexpr->html function. Chances are good that that will give you the HTML you’re after.
txexpr: X-expressions for the rest of us
txexpr library is, in my experience, a really useful tool for working with HTML in Racket.
txexpr is short for
tagged X-expression. I frequently use txexpr in my Racket sites and books (xexprs are the bread and butter of Pollen). (A couple of examples are included below.)
I might even go so far as to say that
txexpr is perhaps the best way to generate HTML for Racket, though I also use and can recommend Scribble for this purpose. (That’s another topic.)
txexpr library offers a few useful functions for generating so-called tagged X-expressions (whence the
txexpr). The main functions are
txexpr*. One gives the name of an element, a list of attributes, and the children, which are themselves xexprs. The value is an xexpr.
The last point is important. Tagged X-expressions are X-expressions. To be more precise: the values returned by
txexpr* are X-expressions. You can combine
txexpr with other utilities for working with xexprs. In other words, by adopting txexpr in your work, you’re not entering a walled garden containing its own offshoot variety of XML. You’re in the standard Racket camp.
Here’s a real-life example how I use txexpr in my servlets:
(define (section #:title [title #f] . elements) (txexpr 'section (cond ((string? title) (list (list 'title title))) (else empty)) elements))
I use this to create a
section element, possibly with a title (which later gets turned into a suitable
Here’s an example of txexpr* and txexpr being mixed:
(define-tag-function (explanation-row attrs elements) (define field (find-elem 'field elements)) (define type (find-elem 'type elements)) (define explanation (find-elem 'explanation elements)) (txexpr* 'tr empty (txexpr* 'td empty (txexpr 'code empty (get-elements field)) (txexpr* 'br) (txexpr 'em empty (get-elements type))) (txexpr 'td empty (get-elements explanation))))
Here I’m building an HTML table row (
tr) having a certain pre-specified format (there are exactly two columns). (In this snippet, it doesn’t matter what
find-elem is (it’s some private function for my own use). All one needs to know is that it returns an xexpr.) Notice that
txexpr* allows an arbitrary number of child node arguments (and, in an extreme case of generating an empty
br element, even allows no arguments at all apart from the element name).
In this example, I’m also using the
get-elements to extract the children of a tagged X-expression.
“But I want HTML, not XML.”
It’s true that when you’re building up, breaking down, and rearranging things using the txexpr functions, you are, in fact, building up, breaking down, and rearranging X-expressions. That is, you’re doing XML work. But we’re not after XML; we’re trying to get HTML. How, then, is txexpr going to help?
For the proposes of generating HTML, the most useful function that
txexpr offers is
xexpr->html. Despite the name, this function resides in the
The main thing that
xexpr->html respects that
xexpr->string does not is the handling of a couple of HTML-y exceptions: the
style elements. For these, the intended use is that they are raw character data (CDATA), from the XML point of view, even though one does not explicitly wrap them in a CDATA. For XML, there’s nothing special about script and style, so Racket’s
xexpr->string is happy to escape all the text within the script and style elements. For HTML, though, these are to be treated differently.