Practical HTML generation in Racket servlets with txexpr
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->htmlfunction. Chances are good that that will give you the HTML you're after.
txexpr: X-expressions for the rest of usMatthew Butterick's
txexprlibrary is, in my experience, a really useful tool for working with HTML in Racket.
txexpris 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
txexpris perhaps the best way to generate HTML for Racket, though I also use and can recommend Scribble for this purpose. (That's another topic.) The
txexprlibrary 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
txexprwith 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.
ExamplesHere's a real-life example how I use txexpr in my servlets:
I use this to create a
(define (section #:title [title #f] . elements) (txexpr 'section (cond ((string? title) (list (list 'title title))) (else empty)) elements))
sectionelement, possibly with a title (which later gets turned into a suitable
h3, etc.). Here's an example of txexpr* and txexpr being mixed:
Here I'm building an HTML table row (
(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))))
tr) having a certain pre-specified format (there are exactly two columns). (In this snippet, it doesn't matter what
find-elemis (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
brelement, even allows no arguments at all apart from the element name). In this example, I'm also using the
get-elementsto 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
xexpr->html. Despite the name, this function resides in the
txexprpackage. The main thing that
xexpr->stringdoes not is the handling of a couple of HTML-y exceptions: the
styleelements. 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->stringis happy to escape all the text within the script and style elements. For HTML, though, these are to be treated differently.