A peek inside RunDMC, Part 2

by Evan Lenz

In part 1, we looked at how RunDMC configures the navigational structure of the website you're viewing. In this post, we'll look at the other major technique used in RunDMC: an XML-based tag library for generating dynamic content.

XML-based custom tag library

The basic idea here is the same as that of other server-side scripting languages such as JSP or ASP. You use HTML templates with special tags that designate dynamic content. In our case, every page of the site uses the same outermost template: template.xhtml. This file contains all the boilerplate that needs to go on every page, such as CSS and JavaScript references and the copyright notice you see at the bottom of the page. For things that vary from page to page, we use custom tags in our own namespace mapped to the "ml" prefix. Thus, for the page content, we have a tag called <ml:page-content/> and for the breadcrumb navigation we have a tag called <ml:breadcrumbs/>.

But this isn't ASP or JSP or even XQuery. How do those tags get replaced with the content for each page? Before I answer that question, let's back up a bit. Recall that RunDMC is an XSLT-based application. In fact, every page of the site is generated using the same XSLT stylesheet (page.xsl). When the browser makes a request for an HTML page on this site, the URL rewriter performs an internal redirect to transform.xqy, which applies page.xsl to the XML source document that was requested.

For example, let's talk about what happens when you visit the "Code" section of this site. When you click a link to "http://developer.marklogic.com/code", the server performs an internal redirect, invoking transform.xqy, which calls the xdmp:xslt-invoke() function to apply page.xsl to code.xml. That's the basic framework: apply page.xsl to whatever page the user requested.

Now that we know that, let's see what page.xsl does. The very first thing that happens is that it processes the master XHTML template, template.xhtml (which is bound to the $template variable). In fact, at least at first, it completely ignores the contents of code.xml.

And here are the default template rules that are invoked as a result (in page.xsl):

In other words, copy everything unchanged (except to strip out the declarations for unused namespaces). Obviously, we don't want to copy everything in template.xhtml. We want to convert our custom tags to some other XHTML content. To do that, we write some more template rules for just that purpose—rules which override the default copying behavior. For example, to generate the top-level navigation menu, we need a rule that matches the <ml:top-nav/> element.

That can be found in navigation.xsl (which page.xsl includes):

By the way, if you're wondering why we didn't include the "ml:" prefix in the match attribute, it's because we don't need to: the tag library namespace is configured as the default, using the xpath-default-namespace stylesheet directive.

The above rule creates an unordered list and gets the list items by processing the contents of navigation.xml (bound to the $navigation variable), creating an item for each top-level <page> element that isn't marked as hidden. That's what results in the horizontal menu we see at the top, which is just a CSS-styled unordered list:

Top menu screenshot

Similarly, the rule for <ml:page-content/> (defined in page.xsl) is as follows (slightly simplified here):

This is how we switch back to our principal source document, code.xml, which is bound to the global variable $content, defined earlier in page.xsl:

Now, let's take a look at the contents of code.xml. Inside the <ml:page> container, we see some XHTML elements (<h1> and <p>), as we'd expect. But it also starts off with a custom element, named <ml:short-description>:

What happens when the <ml:short-description> element gets processed? Well, as it turns out, that's the simplest tag implementation yet. Looking in tag-library.xsl (also included by page.xsl), we see an empty template rule:

In other words, don't do anything. Don't copy it or convert it; just ignore it. So the purpose of this element is not to display its contents on the page. Instead, it's used in another context (try hovering over the "Code" link in the top-level site menu above, and you'll see how the short description is used.)

We'll look at one more custom tag. The content of code.xml also contains this tag:

Until now, most of the custom tags have been simply empty placeholders, but this custom tag is configurable. It's what results in the tabbed feature menu we see on the Code page:

screenshot of tabbed features menu

And the implementation for this custom tag is also found in tag-library.xsl:

In other words, process each child <feature> element twice, once to generate the horizontal tab labels at the top, and once to generate the content that's displayed for each tab. The details of this implementation are farmed out to some other template rules, which you can explore in tag-library.xsl if you're interested.

XSLT is a great language for implementing custom tag libraries. Of course, I'm not the first person to point this out. For more on this topic, check out "Style-free XSLT" and "Template Languages in XSLT."

One advantage of this approach is that all the content is stored in XML and all the logic for generating dynamic content is separated from that. In other words, it helps us achieve "separation of concerns." We generally aimed to minimize the amount of XHTML we put into the XSLT. Both the individual content pages and the outermost XHTML wrapper do not appear in code (XSLT or XQuery) but in "data," i.e. plain old XML files which can be edited by an XML authoring tool.

Comments