The Dracula team is working on tagging locations, technology, dates, times, and more in Bram Stoker’s novel Dracula, and we will work with their project code create a reading view that includes a reading view of the novel as well as an information-rich table of contents to summarize the contents of their tagging so far. If you are working on a different project, you may opt to adapt the code we are modeling with Dracula to your own XML for this assignment.
Open the XML file in <oXygen/> and study its code, and take some notes about where you can find the information destined for the target output in HTML.
It may also help to orient yourself to HTML table coding. HTML tables are organized in rows, using <tr>
elements, which contain <td>
elements (which means table data
). You control the columns in an HTML table by the setting the <td>
cells in an ordered sequence. Inside a <tr>
, the first <td>
is set in column 1, the second <td>
in column 2, the third in column 3, and so on. The top row conventionally contains headings in <th>
cells, which HTML will emphasize by default. Here is a simple HTML table (styled following our newtfire CSS, in which I’ve outlined the borders and given a background color to the table heading cells). Next to it is a view of the HTML code that creates the table structure:
Heading 1 | Heading 2 | Heading 3 |
---|---|---|
Row 1, cell 1 | Row 1, cell 2 | Row 1, cell 3 |
Row 2, cell 1 | Row 2, cell 2 | Row 2, cell 3 |
<table> <tr> <th>Heading 1</th> <th>Heading 2</th> <th>Heading 3</th> </tr> <tr> <td>Row 1, cell 1</td> <td>Row 1, cell 2</td> <td>Row 1, cell 3</td> </tr> <tr> <td>Row 2, cell 1</td> <td>Row 2, cell 2</td> <td>Row 2, cell 3</td> </tr> </table>
To make a table of contents as well as a reading view of the whole Dracula novel, we are introducing modal XSLT, which lets you process the same nodes in your document in two different ways. How can you output the same element contents to sit as table cells in a table of contents at the top of an HTML page, and also as headers positioned throughout the body of your document, below the table of contents? Wouldn’t it be handy to be able to have two completely different template rules that match exactly the same elements: one rule to output the data as list items in the table of contents, and the other to output the same data as headers? You can write two template rules that will match the same nodes (have the same value for their @match
attribute), but how do you make sure that the correct template rule is handling the data in the correct place?
To permit us to write multiple template rules that process the same input nodes in different ways for different purposes, we write modal XSLT, and that is what you will be learning to write with this assignment. Modal XSLT allows you to output the same parts of the input XML document in multiple locations and treat them differently each time. That is, it lets you have two different template rules for processing the same elements or other nodes in different ways, and you use the @mode
attribute to control how the elements are processed at a particular place in the transformation. Please read the explanation and view the examples in Obdurodon’s tutorial on Modal XSLT before proceeding with the assignment, so you can see where and how to set the @mode
attribute and how it works to control processing.
To ensure that the output will be in the XHTML namespace, we
need to add a default namespace declaration (in purple
below). To output the required DOCTYPE declaration, we also need to set the
<xsl:output>
element as a child of our root
<xsl:stylesheet>
element (in blue below), and we needed to include an attribute there to omit the default XML declaration because if the XML line shows up in our XHTML output, it will not produce valid HTML with the w3C and might produce quirky problems with rendering in various web browsers.
So, our modified stylesheet template and xsl:output line is this, and you should copy this into your stylesheet:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0" xmlns="http://www.w3.org/1999/xhtml"> <xsl:output method="xhtml" encoding="utf-8" doctype-system="about:legacy-compat" omit-xml-declaration="yes"/> </xsl:stylesheet>
At first, forget about the modal XSLT and just start by processing the reading view of Dracula. In many ways, for processing Dracula, this is easier than your previous assignments, and it involves processing richly mixed content without needing to target it selectively. Here is what we intend to capture from the Dracula team’s markup (though you may wish to adapt this to your own project markup and make yourself a checklist before you begin):
<title>
element into an HTML <h1>
element.<chapter>
elements, and transform the chapter <heading>
elements into HTML <h2>
elements.<p>
elements from the XML into HTML <p>
elements (which may seem odd, but the HTML version is actually different from the XML because the HTML is coded in an XHTML namespace).span
elements with @class
attributes (remembering our little tutorial on Using <span> and @class to style your HTML): Transform at least two of the following in your XSLT, or as many as you wish:
<location>
<tech>
<date>
<time>
<people>
(Dracula team: You can combine your <person>
and <people>
elements by combining them with an or pipelike this:
<xsl:template match="person | people">
.The template rule for the document node in our solution, revised to output a table of contents with all the information we wish to show before the text of the poems, looks like the following:
<xsl:template match="/">
<html>
<head>
<title>Dracula</title>
<link rel="stylesheet" type="text/css" href="style.css"/>
</head>
<body>
<h1><xsl:apply-templates select="descendant::title"/></h1>
<!--ebb: Table of contents here. -->
<table>
<tr>
<th>Chapter Number</th>
<th>Locations mentioned</th>
<th>Tech mentioned</th>
</tr>
<xsl:apply-templates select="descendant::chapter" mode="toc"/>
<!--ebb: This xsl:apply-templates line sets up my "toc" mode for the table of contents,
so that in the top part of the document we’ll output a selection of the body elements
specially formatted for my Table of Contents, and so that in another section of my document below,
which I’ve put inside an HTML <section> element,
we can also output the full text of the poems with their titles again.-->
</table>
<!--ebb: Reading view of the chapters here. -->
<xsl:apply-templates select="descendant::chapter"/>
</body>
</html>
</xsl:template>
The highlighted code is what we added to include a table of contents, and the important
line is <xsl:apply-templates select="descendant::chapter" mode="toc"/>
. This is
going to apply templates to each chapter with the @mode
attribute value
set to toc
. The value of the @mode
attribute is up to you
(we used toc
for table of contents
), but whatever you call it, setting the
@mode
to any value means that only template rules that also specify a
@mode
with that same value will fire in response to this
<xsl:apply-templates>
element. Now we have to go write those
template rules!
What this means is that when you process the <body>
elements to
output the full text of the chapters, you use <xsl:apply-templates>
and
<xsl:template>
elements without any @mode
attribute.
To create the table of contents, though, you can have
<xsl:apply-templates>
and <xsl:template>
elements that select or match the same elements, but that specify a mode and apply
completely different rules. A template rule for <chapter>
elements in
table-of-contents mode will start with <xsl:template match="chapter"
mode="toc">
, and you need to tell it to create a <tr>
element with some nested td
elements to match the table heading rows you set up earlier:
<xsl:template match="chapter" mode="toc"> <tr> <td><!--Output the chapter heading here. When we are ready we will create a link here to jump from the chapter heading to its target id coded in the chapter heading in the reading view.--> </td> <td><!--Output a string-joined list of distinct locations here?-></td> <td><!--=Output a string-joined list of tech mentioned here?--></td> </tr> </xsl:template>
The rule for processing those same elements not in any mode will start with
<xsl:template match="chapter">
(without the @mode
attribute). That rule will create the HTML <h2>
to output the text of the chapter <heading>
element and then apply-templates again to select the <p>
elements for processing as HTML <p>
elements. In this way, you can have two
sets of rules for this document: one for the table of contents and one to output the full text in a reading view, and we use
modes to ensure that each is used only in the correct place.
Remember: both the <xsl:apply-templates>
, which tells the system
to process certain nodes, and the <xsl:template>
that responds to
that call and does the processing must agree on their mode values. For the main
output of the full text of every chapter, neither the
<xsl:apply-templates>
nor the <xsl:template>
elements specifies a mode. To output the table of contents, both specify the same
mode.
Run-to-Endbutton. Eye-balling those results is not really enough because the Output window does not check for well-formedness or validation against a schema. Be sure to save those results, either by setting an output location in the appropriate place in the selection boxes, or by right-clicking on the output window and selecting
Save as. Always, always open the saved output file in <oXygen/> and check to make sure that it checks out as valid and well-formed. Your new output should open up as well-formed and valid HTML, with a green square in <oXygen>.