Digital humanities


Maintained by: David J. Birnbaum (djbpitt@gmail.com) [Creative Commons BY-NC-SA 3.0 Unported License] Last modified: 2017-02-26T00:23:57+0000


XSLT assignment #2 answers

The assignment

Write an XSLT stylesheet that will transform the XML input document into an HTML document that consists entirely of tables of characters and factions. You can see the desired output at http://dh.obdurodon.org/skyrim-02.xhtml.

Our solution

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns="http://www.w3.org/1999/xhtml" version="2.0">
    <xsl:output method="xml" indent="yes" doctype-system="about:legacy-compat"/>
    <xsl:template match="/">
        <html>
            <head>
                <title>Skyrim</title>
                <style>
                    table { border-collapse: collapse; }
                    table, th, td { border: 1px solid black; }
                </style>
            </head>
            <body>
                <h1>Skyrim</h1>
                <h2>Cast of characters</h2>
                <table>
                    <tr>
                        <th>Name</th>
                        <th>Faction</th>
                        <th>Alignment</th>
                    </tr>
                    <xsl:apply-templates select="//cast/character"/>
                </table>
                <h2>Factions</h2>
                <table>
                    <tr>
                        <th>Name</th>
                        <th>Alignment</th>
                    </tr>
                    <xsl:apply-templates select="//cast/faction"/>
                </table>
            </body>
        </html>
    </xsl:template>
    <xsl:template match="character">
        <tr>
            <td>
                <xsl:apply-templates select="@id"/>
            </td>
            <td>
                <xsl:apply-templates select="@loyalty"/>
            </td>
            <td>
                <xsl:apply-templates select="@alignment"/>
            </td>
        </tr>
    </xsl:template>
    <xsl:template match="faction">
        <tr>
            <td>
                <xsl:apply-templates select="@id"/>
            </td>
            <td>
                <xsl:apply-templates select="@alignment"/>
            </td>
        </tr>
    </xsl:template>
</xsl:stylesheet>
        

Preliminaries

To keep everything in one place for this answer key we’ve used a <style> element in the <head> to hold the CSS that specifies a border for the table. However, in your projects you’ll normally want to declare all CSS rules in a separate CSS stylesheet. The old @border attribute on <table> elements is deprecated in HTML5, which means that although it works in all the browsers, it should be avoided; best practice for specifying a table border is now to use CSS.

Template rules

Before writing any code to extract the information that is going to populate the rows of our tables, we begin by creating the superstructure of our envisaged HTML document, as we did in XSLT assignment #1. As always, the HTML document that we create has an <html> root element with two children, <head> (with <title> and <style> inside it) and <body>. Inside the body, we create a prominent <h1> element to title our page as Skyrim, and then an <h2> subtitle followed by a <table> for the Cast of characters and another <h2> and <table> for Factions. You can read more about HTML tables at http://www.w3schools.com/html/html_tables.asp.

The tables that we are creating need to be filled. HTML tables are constructed row by row, and rows are defined by <tr>. Rows contain cells, of which there are two types: <th> (table header) is used to label a row or column and <td> is used to represent actual data. When, inside our template rule for the document node, we create the start and end tags for the tables themselves, we also create the header rows (<tr> containing <th>), but to populate subsequent rows with character-specific and faction-specific data we need to call on different templates, ones that know how to handle the <character> and <faction> elements that occur in the input XML document.

Our goal is to create a row for every character, and that row should contain three cells, the first with the character’s name, the second with his or her faction, and the third with his or her alignment. Every character and every faction is listed in the <cast> element in our XML document, and so we want to look there to retrieve the data that we’re going to insert into our table rows. In other words, we need to apply templates to each of the <character> and <faction> elements that we find inside <cast> to create rows for the HTML tables we are creating. Since we want to create rows with information about characters after the header row for that table, we put the <xsl:apply-templates select="//cast/character"> element right there, immediately after the first table row inside the table. The value of the @select attribute tells our stylesheet to navigate directly to the <cast>, to find all of its <character> children, and then to apply templates to those <character> elements, putting content exactly where the <xsl:apply-templates> element was. To communicate to the stylesheet the output we desire, we need to define a template for <character> elements that tells our stylesheet specifically what to do.

The template we use has a @match attribute with the value character, which will match any <character> it sees, which in this case means the ones that are thrown at it when the <xsl:apply-templates select="//cast/character"/> element fires inside the first table, the one we create in the template rule for the document node. (It would also match any other <character> element it might see, but it never sees any others, since this stylesheet never applies templates to anything outside the <cast> element.) Inside the template rule for <character> we create a <tr>, which will be inserted into the table we’re creating in exactly the place where the <xsl:apply-templates select="//cast/character"/> element was located, that is, right after the header row. This new row has three <td> elements to hold the data for the character. Those data are retrieved because each of the data cells contains its own <xsl:apply-templates> element, which selects the @id, @alignment, and @loyalty attributes of the particular <character>, respectively. Since all we want from those attributes is their textual value, we don’t have to define our own templates to handle them; we can rely instead on the behavior of the build-in default template, which just outputs the textual content of any element (or, in this case, the value of any attribute) that does not have its own template rule. As a result, each data cell will come to be populated with the string value of the targeted attribute.

We follow a similar process for creating the table listing each faction. We call <xsl:apply-templates select="//cast/faction">, define a template for factions (<xsl:template match="faction"/>), and create a table row inside the template with data cells applying templates to @id and @alignment. Keep in mind that <faction> elements only have two properties, and that our table rows for factions will have only two data cells.

The result

Skyrim

Cast of characters

Name Faction Alignment
UrielSeptim empire blades good
hero neutral neutral
Jauffre empire blades good
MartinSeptim empire blades good
MehrunesDagon daedra evil
MankarCamoran daedra MythicDawn evil

Factions

Name Alignment
MythicDawn evil
blades good
daedra evil
empire good
DarkBrotherhood neutral