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
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
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:
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:
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.