Digital humanities


Maintained by: David J. Birnbaum (djbpitt@gmail.com) [Creative Commons BY-NC-SA 3.0 Unported License] Last modified: 2021-04-28T21:29:57+0000


Test #6: XSLT: answers

The task

In this test, we asked you to use markup from Gawain’s ghost: Examination of cultural allusions in Sir Gawain and the Green Knight, a course project from Spring 2014.

Your XSLT code should have outputted a valid XHTML document that included:

These tasks were based on assignments you had already completed as part of our XSLT homework, including the Shakespearean Sonnets and Skyrim assignments, but were restructured to fit this specific document and encourage you to think about translating output requirements into code that can produce that output.

Our solution

Overview

This is an implementation of XSLT methodology that combines methods and techniques used in the Shakespeare sonnets and the Skyrim homework assignments. As in those assignments:

Code

We’ve used XML comments below in the way we often do in real development, so that if we go back to our code after not having thought about it for a while, or if we share it with someone else, the comments will make it easier to understand.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.w3.org/1999/xhtml"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math" exclude-result-prefixes="#all"
    version="3.0">
    <xsl:output method="xhtml" html-version="5" omit-xml-declaration="no" include-content-type="no"
        indent="yes"/>
    <xsl:template match="/">
        <html>
            <head>
                <title>Sir Gawain and the green knight: sample XSLT test solution</title>
                <link rel="stylesheet" type="text/css" href="2214_xslt-stylesheet.css"/>
            </head>
            <body>
                <!-- ============================================== -->
                <!-- There is no title in the input XML, so we add  -->
                <!-- one manually to our output                     -->
                <!-- ============================================== -->
                <h1 id="top">Sir Gawain and the green knight</h1>
                <h2>Contents</h2>
                <hr/>
                <ul>
                    <!-- ========================================== -->
                    <!-- Use modal XSLT (mode="toc" here) to create -->
                    <!-- links in a table of contents that will     -->
                    <!-- point to the sections in the reading view  -->
                    <!-- The only <div> elements in the document    -->
                    <!-- are the four parts                         -->
                    <!-- ========================================== -->
                    <xsl:apply-templates select="//div" mode="toc"/>
                </ul>
                <hr/>
                <!-- ============================================== -->
                <!-- Apply templates in no mode to create text for  -->
                <!-- reading                                        -->
                <!-- ============================================== -->
                <xsl:apply-templates select="//div"/>
                <!-- ============================================== -->
                <!-- A link back to the top is not strictly         -->
                <!-- but it may make the user experience easier     -->
                <!-- ============================================== -->
                <hr/>
                <p>
                    <a href="#top">Back to top</a>
                </p>
            </body>
        </html>
    </xsl:template>
    <!-- ========================================================== -->
    <!-- Templates in "toc" mode for table of contents              -->
    <!-- ========================================================== -->
    <xsl:template match="div" mode="toc">
        <li>
            <xsl:apply-templates select="head" mode="toc"/>
        </li>
    </xsl:template>
    <xsl:template match="head" mode="toc">
        <!-- ====================================================== -->
        <!-- Use attribute value templates (AVTs) to create links   -->
        <!-- that lead to the beginning of each section. The count  -->
        <!-- of the preceding (not preceding-sibling) <head>        -->
        <!-- elements, incremented by 1, means that the first @href -->
        <!-- value will be "#part1", the second "#part2", etc.      -->
        <!-- ====================================================== -->
        <a href="#part{count(preceding::head) + 1}">
            <xsl:apply-templates/>
        </a>
    </xsl:template>
    <!-- ========================================================== -->
    <!-- Templates in no mode for reading view                      -->
    <!-- ========================================================== -->
    <xsl:template match="div">
        <!-- ====================================================== -->
        <!-- The <section> wrapper around the <h2> label and body   -->
        <!-- of each part is not required, but it's considered good -->
        <!-- practice.If you don't use <section>, you can specify   -->
        <!-- the @id on the <h2>. The @id value must match the      -->
        <!-- corresponding @href value, except that the @href has a -->
        <!-- leading "#" and the @id doesn’t.                       -->
        <!-- ====================================================== -->
        <section id="part{count(preceding::head) + 1}">
            <h2>
                <xsl:apply-templates select="head"/>
            </h2>
            <xsl:apply-templates select="lg"/>
        </section>
    </xsl:template>
    <xsl:template match="lg">
        <!-- ====================================================== -->
        <!-- The document is structured with <lg> (line group)      -->
        <!-- elements that contain <lb/> (line break) empty         -->
        <!-- elements after each line. We replicate that structure, -->
        <!-- but with HTML elements instead of TEI ones: we replace -->
        <!-- <lg> with <p> and (below) <lb/> with <br/>.            -->
        <!--                                                        -->
        <!-- Inside the <p> we create we apply templates to all     -->
        <!-- children of <lg>, both elements and text nodes. We     -->
        <!-- create our own templates only for <ref> and <lb/>; the -->
        <!-- built-in (default) templates for the others will throw -->
        <!-- away the markup and apply templates all the way down   -->
        <!-- to the text.                                           -->
        <!-- ====================================================== -->
        <p>
            <xsl:apply-templates/>
        </p>
    </xsl:template>
    <xsl:template match="lb">
        <!-- ====================================================== -->
        <!-- The input XML had an <lb/> after every line, but there -->
        <!-- is nothing to break after the last line in each <lg>,  -->
        <!-- so ideally you would test for that and suppress the    -->
        <!-- <br/> if there is nothing to break. How to do that     -->
        <!-- isn’t straightforward with this document (as it was    -->
        <!-- with the sonnets) because in this document the lines   -->
        <!-- sometimes inside other markup, so you can't just look  -->
        <!-- for following-sibling <lb/> elements.                  -->
        <!--                                                        -->
        <!-- At a minimum, then, replace every <lb/> in the input   -->
        <!-- with a <br/> in the output; this is not ideal and      -->
        <!-- would leave you with a few extra <br/>s here and there.-->
        <!-- However, these extra <br/>s don't actually affect the  -->
        <!-- appearance of the output, and given the complicated    -->
        <!-- nautre of this structure, this solution is about what  -->
        <!-- we would expect you to come up with. But see below for -->
        <!-- a more robust alternative.                             -->
        <!-- ====================================================== -->
        <!-- This version outputs a <br/> after every line,         -->
        <!-- the last line of each line group. Uncomment it and     -->
        <!-- comment out the alternative, below, to use the simpler -->
        <!-- (but less accurate) one.                               -->
        <!-- ====================================================== -->

        <!--<br/>-->

        <!-- ====================================================== -->
        <!-- This version outputs a <br/> for every <lb/> in each   -->
        <!-- <lg> unless it is the last <br/> in the <lg>. Since    -->
        <!-- the <lb/> elements are not all siblings, we use the    -->
        <!-- following (rather than following-sibling) axis, but we -->
        <!-- restrict the domain to looking only at <lb/> elements  -->
        <!-- that have the same <lg> ancestor, that is, that are in -->
        <!-- the same line group.                                   -->
        <!-- If you use the simpler version (above), uncomment that -->
        <!-- one and comment out this one.                          -->
        <!-- ====================================================== -->
        <xsl:if test="following::lb[ancestor::lg is current()/ancestor::lg]">
            <br/>
        </xsl:if>
    </xsl:template>
    <xsl:template match="ref">
        <!-- ====================================================== -->
        <!-- We again use AVTs, this time to create <span> elements -->
        <!-- with @class values that match the @type values of the  -->
        <!-- original document <ref> elements. Be careful, though;  -->
        <!-- this works only if the original @type values do not    -->
        <!-- contain space characters. You can check this in your   -->
        <!-- XML with an XPath expression:                          -->
        <!--                                                        -->
        <!--   //ref/@type => distinct-values()                     -->
        <!--                                                        -->
        <!-- These <span> elements could be created separately for  -->
        <!-- each specific @type (for the text you needed only      -->
        <!-- three), but letting the AVT do the work for you is     -->
        <!-- less effort and more extensible (you can reuse the     -->
        <!-- code for other, similar documents), and it has less    -->
        <!-- opportunity for user error.                            -->
        <!-- ====================================================== -->
        <span class="{@type}">
            <xsl:apply-templates/>
        </span>
    </xsl:template>
</xsl:stylesheet>

Additionally, you were required to have an associated CSS document that styled, at minimum, three of the <span> @class values you created by transforming the <ref> @type values in the original document. Here is our example, styling every <span> @class possible to create with the above attribute value template expression:

.mythological {
    background:red;
    color:white;
}
.location {
    background:orange;
    color:white;
}
.figure {
    background:gold;
    color:white;
}
.religious {
    background:yellow;
}
.numerological {
    background:lime;
}
.color {
    background:green;
    color:white;
}
.chivalry {
    background:teal;
    color:white;
}
.folklore {
    background:blue;
    color:white;
}
.pagan {
    background:indigo;
    color:white;
}
.circle {
    background:purple;
    color:white;
}
.nature {
    background:fuschia;
    color:white;
}
.wounds {
    background:pink;
    color:white;
}