Well, I am going to use a slighty different format for the promotions. Quite similar, really. This is not a crucial point, anyhow.
An example of a book promotion is:
<book day="13" month="3" year="2001" status='Confirmed'>
<bookTitle url="http://www.amazon.com/exec/obidos/ASIN/0201711036/ref=ase_electricporkchop/107-0402457-1632559">XSLT: Working with XML and HTML</bookTitle>
<author name='Khun+Yee+Fung'>Khun Yee Fung</author>
<publisher url="http://www.awl.com/">Addison-Wesley</publisher>
<forum forum='XML,+XSL,+DOM+and+SAX'>XML, XSL, DOM and SAX</forum>
</book>
For a voucher, an example is:
<voucher day='20' month='02' year='2001' status='Completed'>
<title url="http://suned.sun.com">Sun Certified Java Architect Exam(voucher)</title>
<publisher url="http://suned.sun.com">Sun Educational Services</publisher>
<forum forum="Architect+Certification">Architect Certification</forum>
<winners>
<winner>Bidyut Padhi</winner>
<winner>faisal mahmood</winner>
<winner>shailesh sonavadekar</winner>
<winner>Vishakha Ahuja</winner>
</winners>
</voucher>
The root element for each kind of promotion is different. Again, this is to make it trivial to figure out whether a promotion is about a book, a certificate, or a voucher. In fact, later on, it could be something else. In this way, you can add in different kinds of promotion without rewriting much of the XML documents and XSLT documents.
Since I changed the XML format, I guess I should provide the XSLT documents as well. Actually, I will provide one. The other one is very similar.
Now, let us look at the XSLT document for past winners. It is good it has an xsl:output element. Since the method attribute is set to html, all the BR elements will be output properly. However, I would not set the doctype-public attribute because I believe your output will be only a fragment of the HTML document it is inside. Furthermore, I would set the indent attribute to yes only when debugging the output. Otherwise, I would set it to no. I don't want any browser to mis-interpret any whitespaces, something that the browsers do not handle well.
Now, suppose the lineup file looks like:
<lineup>
<promotion date='20001128'/>
<promotion date='20001219'/>
<promotion date='20010102'/>
<promotion date='20010109'/>
<promotion date='20010116'/>
<promotion date='20010123'/>
<promotion date='20010130'/>
<promotion date='20010206'/>
<promotion date='20010220'/>
<promotion date='20010227'/>
<promotion date='20010306'/>
<promotion date='20010313'/>
<promotion date='20010320'/>
<promotion date='20010327'/>
<promotion date='20010403'/>
</lineup>
I will keep the template that matches the root of the document:
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
I need a template to match the lineup element, so I use what Carl has written for matching 'BookPromotions' as the base. Now, the whole XSLT document looks like:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method='html'/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="lineup">
<TABLE BORDER="1" CELLPADDING="5">
<TR BGCOLOR="#dec7a4">
<TD>Starting Date</TD>
<TD>Book</TD>
<TD>Author(s)</TD>
<TD>Publisher</TD>
<TD>JavaRanch Forum</TD>
<TD>Winners</TD>
</TR>
<!-- to handle each promotion -->
</TABLE>
</xsl:template>
</xsl:stylesheet>
No big deal.
Now, we have to write a template to match promotion. Let us do it bit by bit.
<xsl:template match='promotion'>
<!-- the body of the template-->
</xsl:template>
First, we have to read in the XML document corresponding to the date. This is done by using the document() function in XSLT:
document(concat(@date, ".xml"))/*
There are two ways to handle this external file. One is to use apply-templates to handle the nodes inside. The other is to keep the root element of the document in a variable:
<xsl:variable name='dateTree' select='document(concat(@date, ".xml"))/*'/>
From now on, you can use expressions like $dateTree/@status if you need to find out the status attribute of the root element. Notice that I used '*' because I don't want to know the name of the root element yet.
Now, we have winners only if the promotion is completed. To find out this is the case, we use the xsl:test element. If the condition is true, we output a row in the table. We will also output the date of the event as the first column:
<xsl:if test='$dateTree/@status="Completed"'>
<TR BGCOLOR='#eadbc4'>
<TD NOWRAP='NOWRAP'><xsl:value-of select='$dateTree/@day'/>/<xsl:value-of select='$dateTree/@month'/>/<xsl:value-of select='$dateTree/@year'/></TD>
<!-- the other columns -->
</TR>
</xsl:if>
Now, the title of a promotion depends on the kind of promotion it is. The name of the root element of the promotion document tells us what kind of promotion it is. So we simply apply the right templates for the root element of the document:
<xsl:apply-templates select='$dateTree'/>
And we need one template for each of book, giftCertificate, and voucher. And then we can output the other columns of the row.
First, the template for a gift certificate:
<xsl:template match='giftCertificate'>
<TD NOWRAP='NOWRAP'>
<xsl:value-of select='title'/>
</TD><TD> </TD><TD> </TD>
</xsl:template>
Nothing to it.
The template for a voucher looks like:
<xsl:template match='voucher'>
<TD NOWRAP='NOWRAP'>
<A HREF='{title/@url}'><xsl:value-of select='title'/></A>
</TD>
<TD> </TD>
<TD NOWRAP='NOWRAP'>
<A HREF='{publisher/@url}'><xsl:value-of select='publisher'/></A>
</TD>
</xsl:template>
Again, nothing to it, except the funny {publisher/@url} thing as the value of the HREF attribute. Well, this is an example of an attribute value template. In essence, XSLT allows you to specify the value of an (usually non-XSLT) attribute by enclosing an XPath expression in curly brackets (braces). The long hand for the A element above is:
<A><xsl:attribute name='HREF'><xsl:value-of select='title/@url'/></xsl:attribute><xsl:value-of select='title'/></A>
Of course, attribute value templates are much shorter in general. But it does test your understanding of XPath and location paths though.
Ok, finally, the template for a book promotion:
<xsl:template match='book'>
<TD NOWRAP='NOWRAP'>
<A HREF='{bookTitle/@url}'><xsl:value-of select='bookTitle'/></A>
</TD>
<TD NOWRAP='NOWRAP'>
<A HREF='{concat($common/author/@prefix, author[1]/@name, $common/author/@suffix)}'><xsl:value-of select='author'/></A>
<xsl:for-each select='author[position() > 1]'>
<BR/><A HREF='{concat($common/author/@prefix, ., $common/author/@suffix)}'><xsl:value-of select='.'/></A>
</xsl:for-each>
</TD>
<TD NOWRAP='NOWRAP'>
<A HREF='{publisher/@url}'><xsl:value-of select='publisher'/></A>
</TD>
</xsl:template>
Again, the only funny thing is:
{concat($common/author/@prefix, author[1]/@name, $common/author/@suffix)}
Well, I cheated a little bit. I have to get the elements in the common file somehow. What I did was to add an element at the top of the XSLT document in this way:
<xsl:variable name='common' select='document("common.xml")/*'/>
That is why I can use the variable called common.
The whole expression says to concatenate the prefix attribute of the author element of the element stored in the common variable with the author name and the suffix attribute of the author element of the element stored in the common variable. Quite a mouthful, but rather straightforward.
Another thing: I separate the first author name with the other author names because I want to have a BR element only between author names. I don't want a BR element at the end of the list of names.
With the three templates out of the way, the template of the promotion is not mysterious any more. This is the whole template:
<xsl:template match='promotion'>
<xsl:variable name='dateTree' select='document(concat(@date, ".xml"))/*'/>
<xsl:if test='$dateTree/@status="Completed"'>
<TR BGCOLOR='#eadbc4'>
<TD NOWRAP='NOWRAP'><xsl:value-of select='$dateTree/@day'/>/<xsl:value-of select='$dateTree/@month'/>/<xsl:value-of select='$dateTree/@year'/></TD>
<xsl:apply-templates select='$dateTree'/>
<TD NOWRAP='NOWRAP'>
<xsl:choose>
<xsl:when test='not($dateTree/forum)'>
</xsl:when>
<xsl:otherwise>
<A HREF='{concat($common/forums/@prefix, $common/forums/forum[@name=$dateTree/forum/@forum]/@number, $common/forums/@suffix)}'>
<xsl:value-of select='$dateTree/forum'/></A>
</xsl:otherwise>
</xsl:choose>
</TD>
<TD NOWRAP='NOWRAP'>
<xsl:value-of select='$dateTree/winners/winner[1]'/>
<xsl:for-each select='$dateTree/winners/winner[position() > 1]'>
<BR/><xsl:value-of select='.'/>
</xsl:for-each>
</TD>
</TR>
</xsl:if>
</xsl:template>
The other XSLT document is quite similar. I will leave that as an exercise.
Oh, below is the whole common.xml file. Obviously, I don't have the full set of forum numbers for JavaRanch. I just inserted enough to handle the promotions.
<common>
<author prefix='http://www.javaranch.com/cgi-bin/ubb/ubbmisc.cgi?action=getbio&UserName='/>
<forums prefix='http://www.javaranch.com/cgi-bin/ubb/forumdisplay.cgi?action=topics&forum=&number=' suffix='&DaysPrune=1000&LastLogin='>
<forum name='Servlets+and+JSP|APO|s' number='7'/>
<forum name='Star+Office,+et+al.' number='14'/>
<forum name='Java+in+General+(beginner)' number='33'/>
<forum name='Java+in+General+(advanced)' number='34'/>
<forum name='Performance' number='15'/>
<forum name='XML,+XSL,+DOM+and+SAX' number='31'/>
<forum name='OO,+Patterns,+UML+and+Refactoring' number='9'/>
<forum name='Other+Java+APIs' number='45'/>
<forum name='Architect+Certification' number='26'/>
<forum name='Process:+UP,+RUP,+DRUP,+XP,+etc.' number='42'/>
</forums>
</common>
Now, we are back to the beginning. Like Carl said, what is the next step? Well, for these two pages, there is really not much else that you want to do, other than setting up a batch file so that whenever you finish generating the HTML pages, you move them over to the place where their JSP pages can pick them up.
These are the files I have: common.xml, the individual promotion files, winners.xsl, and lineup.xml. No idea how to send them over if you want them.
------------------
Khun Yee Fung
Author of
XSLT: Working with XML and HTML