XSLT Cookbook: Creating a HTML Calendar


I provide space and support for Condensed Comics. Originally, Mia said she wanted something akin to Keenspace’s site layout and page generation tools; I figured I could do something slightly better.

The original design used key-value database files and custom page templates (driven by Perl, natch); the pages were regenerated each night by a cron script. Both the kludge-nature of the script and the opaqueness of the database bothered me; I wanted something that Mia could edit directly if she needed.

The second version (Danger! Danger!) used a single XML file and multiple XSLT files to generate the pages and syndication files. The majority of the code is in one top-level template, a direct translation of macro variables to value-of elements with complicated preprocessing steps in sub-templates.

Then there was the calendar.

The original Perl code to generate the calendar was a Byzantine nightmare of lists of hashes of lists and there was no obvious analogue in XSLT to the complicated iterations performed.

How soon one forgets.

XSLT is a functional language, even though it (currently) lacks the ability to treat functions (templates) like first-class objects. In functional languages, iteration (cycling over for loops) is replaced by recursion (calling the same function with different arguments).

With that (embarrassing) revelation, the calendar became easy to write, and much more concise than its Perl counterpart. In my defense, back-migrating the XSLT to Perl would probably result in a similar improvement; being forced to program functionally has its advantages.

The other problem was date calculations; fortunately, the EXSLT.org date-time extensions are available in the XSLT processor I use (libxslt from GNOME). EXSLT.org provides template versions of most of their date-time calculators, so in theory I could have done the whole thing in “pure” XSLT, but why bother? The XSLT used is not exposed to the end-user.

The Code

The main template is called with a single parameter today which holds the date of the page to be generated. It creates the outer table element and generates a header line (a row spanning the entire with of the table) containing the name of the month, the year, and links to the preceding and following months. It also determines the Sunday of the week in which the month starts and passes that to the workhorse of the stylesheet, calendar-week. The preceding-month and following-month templates generate hyperlinks to the last comic before this month and the first comic after this month, respectively.


calendar-week recursively calls itself until the month of the Sunday of the following week differs from for-month, at which point it exits. It may have been slightly more efficient (in terms of memory) to do tail-recursion; however, the cost would have been excessive copying of the result tree. As the depth of this recursion will never exceed 6, the danger of overflowing the stack is small. Note that calendar-week does not let calendar-day perform the recursion; since the number of iterations is fixed and small, unrolling the loop seemed to be the obvious optimization.

Note: if Mia has a year-long dry spell, this template is screwed.



Most of calendar-day is elided here, as it is specific to Condensed Comics. Note that since the recursion was unrolled in calendar-week above, the template is straightforward:



And there you have it: a table with each cell containing a number corresponding to the day of the month. The cells can have three possible classes: this-day, corresponding to the date originally passed into the template; this-month, corresponding to those days that are in the same month as that date; and other-month, the “filler” days at the beginning and ending of the calendar so no cells are left empty. Changing the code so that those cells appear empty is left as an exercise to the reader.

I’m horrific when it comes to self-describing variable names, using short forms such as “first” instead of the more informative “first-in-week.” Peter Baxter pointed out this error, and it has been corrected.