NewtFire logo: a mosaic rendering of a firebelly newt
newtFire {dh}
Creative Commons License Last modified: Friday, 06-Nov-2020 07:42:36 UTC. Maintained by: Elisa E. Beshero-Bondar (eeb4 at psu.edu). Powered by firebellies.

Overview of the Assignment

The Fall 2020 Historic Recipes project team is coding recipes to investigate spice trade ingredients, and their code gives us a good opportunity to experiment with pulling ingredient data with XSLT to output in the format of an HTML table. This exercise will help orient you to the code for an HTML table to organize project data.

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. While we think most of the XPath involved will be pretty straightforward, there be at least one challenging step: to try to locate with XPath the steps in the recipe that mention a given ingredient.

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>

Before You Begin: Set up the XSLT Stylesheet to Output HTML

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>

Guide to writing the XSLT

Our XSLT transformation has multiple template rules (xsl:template elements).

  1. As usual when preparing HTML from XML, we set a template rule for the document node (<xsl:template match="/">), in which we create the basic HTML file structure: the <html> element, <head> and its contents, and <body>—anything structure that appears just once in the HTML document (in a one-to-one relationship with the root node).
    1. Inside the <body> element that we’re creating, we use <xsl:apply-templates> and use the @select attribute to identify an element on the tree that we want to process at this point. Here we use a literal XPath expression as the value of the @select attribute. To start building the first HTML table, we set up the outermost <table> element here and set an xsl:apply-templates with @select to direct XSLT to the part of the XML document holding the ingredients. We also added the special row (<tr>) of the table containing the table headings, using the special th elements: holding the text: Ingredient id, Type, Quantity, Unit, Step(s) used in recipe.
    2. In the same template matching on the document node, we also wrote more HTML outer-level structural elements to hold other parts of the recipe, and used xsl:apply-templates with @select to process the appropriate portion of the source XML file for the equipment information and the recipe instructions. We opted to output the equipment information as an unordered list with the HTML ul element, and to number the instructions steps, we output an HTML ordered list with ol.
  2. We created a new xsl:template elements to match on the <item> elements inside the Ingredients list in the source XML. Inside that template rule, we set a table row to contain table data cells holding the output we want, to hold the Ingredient xml:id, its type, quantity needed, unit, and finally (the most challenging part): the corresponding steps that reference it in the recipe instructions.
    • To do this last challenging part, think about how to retrieve the information you need. You want to set xsl:apply-templates to select the a step in the instructions list, so start by reaching for that from the point of view of the current context node (which is the value of your xsl:template @match attribute). Step to the instructions list (how do you get there from here?)
    • Now, think about what you want to find: a step number (how is that coded?). You don't want any step: you need to filter the steps by whether they contain the ingredient listed. How is that coded?
    • Construct an XPath expression that travels from where you are in the template rule, to isolate the step that holds mention of the ingredient, such that the substring-after() the # on the @ptr attribute value equals the @xml:id of the node you are currently processing. NOTE: To access the exact, specific node you are currently processing, you need a special XSLT function: current(). You can reference the @xml:id on the current item in the ingredients list like this: current()/@xml:id. You might want to just set this to check the ./@xml:id, and not see any results because XSLT doesn't actually know which of all the ingredients list items to be checking. That is why we have the current() function, to identify the precise node being processed.
  3. Finally, we created new xsl:template elements to match on the other kinds of items in the recipe: those in the Equipment list and those in the Instructions. And we set their contents to output HTML list items (<li>) to output their contents.
  4. You could take this one step further, as we did, to write a template rule to process the ingredients references in the recipe, and wrap them in an HTML <span> element with a @class referencing the type of ingredient mentioned (which you can find with an XPath reaching the corresponding member of the ingredients list. If you write CSS over this file, you can use this to color code the ingredients by their type. (Read more about it on our short tutorial for using span and class for styling your HTML.

Important