Section 3 Notes
by Blake Meike, former teaching fellow
Menu
You've probably noticed that this project doesn't involve Java at all.
Since there is no need to compile anything, there's really no need
for ant. We've provided it as a handy way to run Xalan, but, underneath
the covers, it is doing this:
java -cp whereever/you/put/xalan.jar org.apache.xalan.xslt.Process
The static main method, in the Process class, looks for the following arguments
- -IN input-file-name
- -XSL xsl-script-file-name
- -OUT output-file-name
- -PARAM param-name param-value
For instance, you would transform the blockbuster.xml file, as required
in the first part of the assignment, from the project directory,
with the following (on a single line):
java -cp whereever/you/put/xalan.jar org.apache.xalan.xslt.Process
-IN myblockbuster/xml/myblockbuster.xml
-XSL myblockbuster/xsl/myblockbuster.xsl
-OUT myblockbuster/xhtml/myblockbuster.html
A template matches what you say it matches, in the "match" attribute.
Refering to the document below, e.g., a template
with the match clause: match="/condo/owner" will be instantiated
three times, once for each of the owner elements.
There are a three of places that might need further discussion.
The first is a template that doesn't have a match attribute. Such
a template must have a name attribute (e.g., name="fmtTable"), and
must be instantiated, explicitly, with an "xsl:call-template" element.
The second place that the match behaviour is interesting, is in
context. Suppose some template, with the match attribute:
match="/condo/unit" contained the following code:
<xsl:apply-templates select="rooms/room">
The templates that will be applied are those that have a match
attribute for "room", not those that match "/condo/unit/rooms/room"
or even those that match "rooms/room". Makes sense, huh?
Finally, there are the default templates. A good way to think about these
is to consider, "how does an XSL engine get started?" Notice that, if you
run Xalan on some XML document, it prints the document. Why?
The answer is that XSL engines have default templates, and some rules
for applying them. The "rules" part says, essentially, that the most
specific rule that applies is the one that is used. Here are the templates:
<xsl:template match="*|/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="text()|@*">
<xsl:value-of select="."/>
</xsl:template>
These are fiendishly clever. The first one applies to the root
element, and then applies itself to every sub-element of the root,
and so on, except when the element is text or an attribute. In
the latter case, the second rule is more specific (text() is more
more specific that *), so it applies.
Of equal importance is that match="/" is more specific than the first
default rule. If you put a template with a match="/" attribute
in it, into your stylesheet, it will apply, instead of the default
template, because it is more specific than "*|/".
Suppose that you you wanted to select all of the rooms in the sample
document below, and put them in an html table with three columns.
Selecting the rooms is easy: select="/condo/unit/rooms/room".
But how would we format them like so:
<table>
<tr>
<td>Kitchen</td>
<td>Bath</td>
<td>Living Room</td>
</tr>
<tr>
<td>Study</td>
<td>Bed Room</td>
<td>Kitchen</td>
</tr>
.
.
.
You might want to stop here and have a shot at this, before you
go on. It turns out to be a bit tricky. If you are like most of us
you'll find yourself trying to use count() or position() in ways that,
since they are part of XPath, they cannot be used.
One solution will look familiar to anyone who has worked with
Lisp (or another functional programming language. Here's most of it:
<xsl:template match="/">
<table>
<xsl:call-templates name="fmtTable">
<xsl:with-param name="rooms" select="condo/unit/rooms/room" />
</xsl:call-templates>
</table>
</xsl:template>
<xsl:template name="fmtTable">
<xsl:param name=rooms"/>
<tr>
<xsl:for-each select="$rooms[position < 3]">
<td><value-of select="." /></td>
</xsl:for-each>
</tr>
<xsl:call-template name="fmtTable">
<xsl:with-param name="rooms" select=$rooms[position >= 3] />
</xsl:call-template>
</xsl:template>
Now, this fragment has the unfortunate problem that it will recur until it
runs out of space. But you can see how to fix that, right?
You should be able to write a couple of different expressions to
obtain each of the following:
- All of the owners that have Dining Rooms
- All of the rooms on the first floor
- The floor on which each of the Kitchens is situated
- Owners that have more than one Bath
- A list of unique room names (no duplcates!)
One solution for the first exercise is:
/condo/unit/rooms/room[.="Dining Room"]/../../owner
But, you, of course, can do much better...
The last one is also pretty tricky. To solve it, you might consider what
it means to be "unique". Perhaps, one way to get the unique items from
the list would be to order the list of all rooms -- in fact, they already
have a "natural" order -- and then you might walk the list. As you reach
each new node, you might keep it, only if it was different from everything
you'd seen already.
<condo>
<unit id="1">
<owner>Rachel</owner>
<rooms>
<room floor="1">Kitchen</room>
<room floor="1">Bath</room>
<room floor="1">Living Room</room>
<room floor="1">Study</room>
<room floor="1">Bed Room</room>
</rooms>
</unit>
<unit id="2">
<owner>Lou</owner>
<rooms>
<room floor="2">Kitchen</room>
<room floor="2">Bath</room>
<room floor="2">Living Room</room>
<room floor="2">Study</room>
<room floor="2">Bed Room</room>
</rooms>
</unit>
<unit id="3">
<owner>Blake & Cathy</owner>
<rooms>
<room floor="1">Kitchen</room>
<room floor="1">Bath</room>
<room floor="1">Dining Room</room>
<room floor="1">Living Room</room>
<room floor="2">Study</room>
<room floor="2">Study</room>
<room floor="2">Bath</room>
<room floor="2">Bed Room</room>
</rooms>
</unit>
</condo>