Main Points

This page revamped the XML file in order to sort series titles and book titles in one list. The <seriesName> tag was dropped and a <title> tag added to the series items with the complete name of the series. The format="book" attribute on the series items were changed to format="series", and the format="book" attributes restored to the individual volumes, just like non-series books. This resulted in a much more standardized XML layout, and the attribute of the item can be checked for the value series. If it exists then we begin the process of pulling it's volumes. There is a key change to the basic structure of the XSLT file, however. Let's start there.

Output Structure

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes" />
<xsl:template match="/">
.
.
.
</xsl:template>
</xsl:stylesheet>

The <xsl:stylesheet> and <xsl:template> tags are the same. However, we've introduced a new tag, <xsl:output>, which more explicitly defines how the XML is transformed onto the page. On the webpage, there's no visual difference. The XML is written into the HTML markup and styled using CSS so everything looks the same with or without it. However <xsl:output> has a huge impact on the look of the XHTML code that's output. The less important attributes, method="xml" version="1.0" encoding="utf-8", ensure that the transform outputs the same style of XML that we're putting into it. The indent="yes" is the whole reason I included this tag. Without it, the transform outputs a sticky mass of XHTML all balled up...

<?xml version="1.0" encoding="utf-8"?><h3 id="titlesBySeries" tabindex="0">Titles by Series</h3><ul><li>This is a book. Atlas of World History</li><li>This is a book. Breaking the Maya Code</li><li>This is a book. Flash 8: Projects for Learning Animation and Interactivity</li><li>This is a SERIES, the attribute says so. The Byzantium series<ul><li>Byzantium: The Early Centuries (Byzantium. Volume 1)</li><li>Byzantium: The Apogee (Byzantium. Volume 2)</li><li>Byzantium: The Decline and Fall (Byzantium. Volume 3)</li></ul></li><li>This is a book. The Crusades Through Arab Eyes</li>

:( Yeah... The indent attribute transforms this impossible to read wad of XHTML into a somewhat structured document like you'd expect from a junior programmer...

<?xml version="1.0" encoding="utf-8"?>
<h3 id="titlesBySeries" tabindex="0">Titles by Series</h3>
<ul>
<li>
This is a book. Atlas of World History</li>
<li>
This is a book. Breaking the Maya Code</li>
<li>
This is a book. Flash 8: Projects for Learning Animation and Interactivity</li>
<li>
This is a SERIES, the attribute says so. The Byzantium series<ul><li>Byzantium: The Early Centuries (Byzantium. Volume 1)
</li><li>Byzantium: The Apogee (Byzantium. Volume 2)
</li><li>Byzantium: The Decline and Fall (Byzantium. Volume 3)
</li></ul></li>
<li>
This is a book. The Crusades Through Arab Eyes</li>
.
.
.

By comparison, this is at least readable, and the indenting is some rough attempt at conveying the structure of the markup. Not marvellous, but a Vast improvement.

Skip to Main Points

Books and Series Alphabetized Together

<h3 id="titlesBySeries" tabindex="0">Titles by Series</h3>
<ul>
<xsl:for-each select="Bibliography/item">
<xsl:sort select="title" />
<li>
.
.
.
</li>
</xsl:for-each>
</ul>

Inside the template is the actual XHMTL we want to output. We start with a level three heading to introduce our list of books and series. Then we begin a list, and each list item is populated by a for-each loop. We select each <item> tag that's a child of the root tag, <Bibliography>. This grabs all the stand alone books and the series names. We then use a sort to order them by titles. I had hoped to be able to sort them as you would traditionally sort a Bibliography, ignoring initial A, An, and The. Unfortunately, ASP.NET won't allow you to use XSLT 2.0 which gives you the ability to use regular expression which is how all the examples do this. I tried various alternatives unsatisfactorily, and finally shelved this issue for now. Who knows, if I wait long enough, .NET will doubtless include 2.0 and then it will be easy. ;) Once we reach the list item tags, <li> it starts to get more complicated. See how it outputs and you can see the target we were shooting at.

Titles by Series

  • This is a book. Atlas of World History
  • This is a book. Breaking the Maya Code
  • This is a book. DHTML and CSS Advanced
  • This is a book. Flash 8 Accelerated
  • This is a book. Flash 8: Graphics, Animation, & Interactivity
  • This is a book. Flash 8: Projects for Learning Animation and Interactivity
  • This is a book. Gone for Soldiers
  • This is a book. Hubble: The Mirror on the Universe
  • This is a book. Paris 1919: Six Months That Changed the World
  • This is a SERIES, the attribute says so. The Byzantium series
    • Byzantium: The Early Centuries (Byzantium. Volume 1)
    • Byzantium: The Apogee (Byzantium. Volume 2)
    • Byzantium: The Decline and Fall (Byzantium. Volume 3)
  • This is a book. The Crusades Through Arab Eyes
  • This is a SERIES, the attribute says so. The Lord of the Rings series
    • The Fellowship of the Ring (Lord of the Rings. Volume 1)
    • The Two Towers (Lord of the Rings. Volume 2)
    • The Return of the King (Lord of the Rings. Volume 3)
  • This is a SERIES, the attribute says so. The Marcus Didius Falco series
    • Silver Pigs (Marcus Didius Falco. Volume 1)
    • Shadows in Bronze (Marcus Didius Falco. Volume 2)
    • Venus in Copper (Marcus Didius Falco. Volume 3)
    • Iron Hand of Mars (Marcus Didius Falco. Volume 4)
    • Poseidon's Gold (Marcus Didius Falco. Volume 5)
    • Last Act in Palmyra (Marcus Didius Falco. Volume 6)
    • Time to Depart (Marcus Didius Falco. Volume 7)
    • A Dying Light in Corduba (Marcus Didius Falco. Volume 8)
    • Three Hands in the Fountain (Marcus Didius Falco. Volume 9)
    • Two for the Lions (Marcus Didius Falco. Volume 10)
    • One Virgin Too Many (Marcus Didius Falco. Volume 11)
    • Ode to a Banker (Marcus Didius Falco. Volume 12)
    • A Body in the Bathhouse (Marcus Didius Falco. Volume 13)
    • The Jupiter Myth (Marcus Didius Falco. Volume 14)
    • Scandal Takes a Holiday (Marcus Didius Falco. Volume 15)
    • The Accusers (Marcus Didius Falco. Volume 16)
  • This is a SERIES, the attribute says so. The US Civil War series
    • Gods and Generals (US Civil War. Volume 1)
    • The Killer Angels (US Civil War. Volume 2)
    • The Last Full Measure (US Civil War. Volume 3)
  • This is a SERIES, the attribute says so. The US Revolution series
    • Rise to Rebellion (US Revolution. Volume 1)
    • The Glorious Cause (US Revolution. Volume 2)
  • This is a book. Til We Have Faces
  • This is a book. Web Design: Tools and Techniques
  • This is a book. Web Designers Reference: An Integrated Approach to Web Design with XHTML and CSS
  • This is a book. World Atlas
  • This is a book. Writing On Both Sides of the Brain
Skip to Main Points

XSLT Revealed

This is where we put most of our work in for this go round, building the list items for the main list. Part of the complication is the conditional nested lists that should be included for series.

<li>
<xsl:choose>
<xsl:when test="series">
.
.
.
</xsl:when>
<xsl:otherwise>
This is a book. <xsl:value-of select="title" />
</xsl:otherwise>
</xsl:choose>
</li>

Here we introduce our first xsl:choose operation. It let's us choose between different options. The first option is to use an <xsl:when> tag to test if the item contains a <series> tag. This tells us if it's a series, and we then have a much more detailed process to handle the series and all of its volumes. That's what the first choice is. If the object does not contain a series tag, it had not been dealt with. We want to include all items so we use an <xsl:otherwise> tag to specify what to do with the rest of the objects. Since anything which isn't a series, will be a stand alone title, we simply display the title. And since all this is inside an <xsl:for-each> tag sorted by title, all of the series and book titles are intermixed in perfect alphabetical order. This resolves the key concern of page 3, and why we had to rewrite the XML file; stand alone books and series needed to be sibling elements and books in a series needed not to be. It seemed most reasonable then to nested them inside the series elements.

There's another interesting wrinkle in this. There's actually a display difference between the browsers. The first one I've encountered in XML. The <xsl:when test="series"> checks to see if there is a <series> tag which is a child of the <item> tag selected. This grabs all of the books and series objects, ie the items which contain a <series> tag. Another loop then does processing to grab the series, but not the volumes and continues into another subloop. More on that later. The further processing generates output only for the series entries, not the volumes, so the individual volumes return null values. In Firefox and Opera, that's the end of the story, the null values get thrown out. However... IE is a different story.

IE outputs the null values as empty list items. It doesn't throw them out, resulting in a lot of empty bullet points. (though interestingly, we determined the empty bullets are ordered by title). We were able to fix this issue by eliminating the individual volumes from the original search. We took the preceding for-each loop above and respecified it using an absolute path, <xsl:for-each select="Bibliography/item">. Yes, I know it was only last test that we removed the absolute reference. But that accomplished the display we wanted before we revamped the XML file. In this code, that was the only tweak necessary to resolve the cross browser display problem. The relative path we used on page three worked fine for resolving those issues but not so well here. So out it goes. Next we had to program the series titles to display and create a nested list for each with the books listed in volume# order. We accomplished this with another <xsl:choose>.

<xsl:choose>
<xsl:when test="@format = 'series'">
This is a SERIES, the attribute says so. <xsl:value-of select="title" />
<ul>
<xsl:for-each select="item">
<xsl:sort data-type="number" select="volume" />
<li>
<xsl:value-of select="title" /> (<xsl:value-of select="series" />. Volume <xsl:value-of select="volume" />)
</li>
</xsl:for-each>
</ul>
</xsl:when>
</xsl:choose>

Dealing with the series entries are a little trickier. Since a <series> child tag could indicate a series object or the volumes of that series, we need to specify the series themselves. Otherwise the volumes are listed not only underneath their respective series but a second time alphabetized with the stand alone titles. Therefore we specify a second <xsl:choose> which checks the format attribute. The series object has a format of "series" and passes the test, while the volumes which are format="book" are weeded out. We then declare that we have located the series by it's attribute and list the title of the series to prove it. As a sublist of each series, we NOW want to list the individual volumes in series order. So we create a new list with a for-each loop inside to grab each volume. This means selecting the <item> child tags for the series and sorting them by volume number. We have to specify the data-type as a number, otherwise it alphabetizes the volumes in order as 1, 10, 11, 2, 3, 4... Finally, we create each list item tag, <li> with the following format. Title (Series. Volume volume#)

Note: There was a dubious flirtation with listing the series entries as independent items. At the time, the individual volumes didn't seem to require nesting inside the series items. However, it proved beyond our ability to collect only the volumes for a specific series, so each series wound up with every volume of every series. So I renested the <item> elements of series volumes as children of the appropriate series. This better conveys the relationship between the data in any case; we probably shouldn't have changed it in the first place.

Skip to Main Points
Continue to 5. Annotated Bibliography
← ← Jump back to XML / XSLT Play