Mapping a Schema to a Web Page

In many cases, prototypes of the planned web pages already exist and we want to "put the data into them". In these cases, the use of rule-based XSLT is hardly appropriate, and it is better to use XSLT in a procedural style.

The first step is to map the nodes from the XML schemas to the web page elements. During this step we can also make sure that all vital information is present in the web page.

Frequently, a web page does not exactly match an XML document type:

  • The web page may not contain all the data of an XML document type;

  • The web page may combine data from several XML document types, as shown in the following example.

Let us assume that a prototype as shown on the right already exists, and that we want to map the web page elements to XML nodes from the schemas shown on the left.

graphics/gui-mapping.png

As far as the data from the album schema is concerned, the mapping from XML to HTML is straightforward. We simply put the whole HTML structure into an XSLT root template, and replace the example data elements with xsl:value-of instructions specifying the corresponding node in the album instance – a simple fill-in-the-blanks technique. In cases where an element has minOccurs and maxOccurs settings other than "1", we enclose it (and its decoration) in an xsl:for-each block. Other HTML elements that depend on the existence of XML nodes are enclosed into an xsl:if block.

Original HTML Source Code

Here is the HTML source code for our prototype as generated by the HTML editor:

<html>
  <head>
    <meta http-equiv="Content-Type"
         content="text/html; charset=utf-8">
    <meta http-equiv="Content-Language" content="en-us">
    <meta name="GENERATOR" content="Microsoft FrontPage 4.0">
    <meta name="ProgId" content="FrontPage.Editor.Document">
    <title></title>
   </head>
   <body bgcolor="#000000" link="#FFFFCC" vlink="#C0C0C0" >
     <table>
       <tr font color="#FFFFCC">
         <td colspan="2" align="right">
           <a href="???">Musicians</a>
           <a href="???">Bands</a>
         </td>
       </tr>
       <tr>
         <td>
           <table>
             <tr bgcolor="silver">
               <td bgcolor="#FFFFCC">
                 <h2>Blues House Jam</h2>
               </td>
               <td rowspan="4">
                 <img src="post-election-jam.jpg"
                      alt="Blues House Jam">
               </td>
             </tr>
             <tr>
               <td bgcolor="#FFFFCC" valign="top">
                 <p>recorded at the <a href="???">
                 <img border="0" src="arrow.gif">
                 </a>Post-Election-Jam</p>
                 <p>October 21, 1947<br>
                 Whitehouse</p>
                 <p><a href="???"><img border="0" src="arrow.gif"></a>
                 Dizzy Gillespie<br>
               </td>
             </tr>
             <tr>
               <td bgcolor="#FFFFCC">
                 Publisher:<br>
                 ProductNo: BGJ-47
               </td>
             </tr>
           </table>
         </td>
       </tr>
       <tr>
         <td><h3><br>
           <font color="#C0C0C0">Tracks</font></h3>
           <table width="100%">
             <tr>
               <td bgcolor="#FFFFCC">1-Post Election Jam I</td>
               <td align="Right" bgcolor="#FFFFCC">19:35</td>
             </tr>
         </table>
       </td>
     </tr>
     <tr>
       <td><h3><br>
         <font color="#C0C0C0">Reviews</font></h3>
         <table width="100%">
           <tr>
             <td bgcolor="#FFFFCC">
               <a href="???"><img border="0" src="arrow.gif">
               </a>Glenn Astarita -
             March 21, 2001</td>
           </tr>
         </table>
       </td>
     </tr>
   </table><br>
  </body>
</html>

Adapting to XHTML

However, most HTML editors produce HTML which, in contrast to XHTML, is not well-formed XML. This requires a few fixes to the source code. Empty elements such as <br> and <img ... > must be properly closed with a slash (<br/> and <img ... />). Also, an entity reference such as &nbsp; must be replaced by a valid XML entity (&#32;) or by <xsl:text> </xsl:text>.

Retrieving XML Data from Tamino

The stylesheet that we create from the HTML prototype must also contain logic to join the album data with collaboration and jazzMusician data. We do this with the help of the XPath function document(), as explained in the chapter From Conceptual Model to Schema::Constraints Across Documents. We read the corresponding collaboration and jazzMusician documents into XSLT variables. The following code extracts the information that we want to display on the web page, such as the name of a collaboration, the date and location of a performance (when the collaboration is a jam session), etc. From jazzMusician documents we extract the first name, middle name, and last name.

Generating Navigation

This data is also used to generate link URLs to the corresponding collaboration and album documents. When the user clicks on these links, the user leaves the album web page and moves to a collaboration or jazzMusician web page. These web pages are also dynamically generated: the link consists of a URL to Tamino with a query part identifying the respective document in the database, plus an appropriate stylesheet to be processed by Tamino's pass-thru servlet.

The Resulting Stylesheet

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
     xmlns="http://www.softwareag.com/tamino/doc/examples/models/jazz/encyclopedia"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="html" indent="yes"/>
<!-- define a constant for the tamino query string -->
<xsl:variable name="query">
     http://localhost/servlets/com.softwareag.tamino.api.servlet.TaminoFilter/tamino/jazz/encyclopedia?_XQL=
</xsl:variable>
<!-- define constant for pass-thru string -->
<xsl:variable name="sheet">&_xslsrc=xsl:stylesheet/</xsl:variable>
<!-- define constant for encyclopedia collection -->
<xsl:variable
    name="ency">http://localhost/tamino/jazz/encyclopedia</xsl:variable>
<!-- Just a single rule for the root node -->
<xsl:template match="/">
<!-- Generate HTML document root -->
<html>
  <head/>
   <body bgcolor="#000000" link="#FFFFCC" vlink="#C0C0C0" >
     <!-- Top level loop. Remember that we get raw Tamino output. -->
     <xsl:for-each select="//album">
     <!-- Perform a join for the collaboration -->
     <xsl:variable name="c_query" select=
     "concat($query,'collaboration[result/@albumNo="',@albumNo,'"]')"/>
     <xsl:variable name="collab"
                   select="document($c_query)//collaboration"/>
     <table>
       <tr>
         <td colspan="2" align="right">
           <font color="#FFFFCC">
             <a href=
   "{concat($query,'jazzMusician',$sheet,'jazzMusician-index.xsl')}"
               Musicians</a>
             <a href=
"{concat($query,'collaboration[@type="band"]',$sheet,'band-index.xsl')}"
                Bands</a>
           </font>
         </td>
       </tr>
       <tr>
         <td>
           <table>
             <tr bgcolor="silver">
               <td bgcolor="#FFFFCC">
                 <h2><xsl:value-of select="title"/></h2>
               </td>
               <xsl:if test="coverImage">
                 <td rowspan="4">
                   <img src="{concat($ency,'/images/',coverImage)}"
                        alt="{title}"/>
                 </td>
               </xsl:if>
             </tr>
             <tr>
               <td bgcolor="#FFFFCC" valign="top">
                 <xsl:choose>
                   <!-- special treatment for jam sessions -->
                   <xsl:when test="$collab/@type='jamSession'">
                     <p>recorded at the
                        <xsl:value-of select="$collab/name"/></p>
                     <p>
                     <!-- call the template for date formatting -->
                     <xsl:call-template name="format-date">
                       <xsl:with-param name="date"
                                 select="$collab/performedAt/time"/>
                     </xsl:call-template>
                     <br/>
                     <xsl:value-of select=
                                   "$collab/performedAt/location"/>
                     </p>
                   </xsl:when>
                   <!-- otherwise print the band/project name -->
                   <xsl:otherwise>
                   <h4>
                     <!-- link includes specification of style sheet -->
                     <a href=
                        "{concat($c_query,$sheet,'collaboration.xsl')}">
                       <img border="0"
                            src="{concat($ency,'/images/arrow.gif')}"/>
                     </a>
                     <xsl:value-of select="$collab/name"/>
                   </h4>
                   </xsl:otherwise>
                 </xsl:choose>
                 <p>
                 <!-- Loop over all collaborateurs -->
                 <xsl:for-each select="$collab/jazzMusician">
                   <!-- Perform the join for the jazzMusicians -->
                   <xsl:variable name="m_query" select=
       "concat($query,'jazzMusician[@ID="',@ID,'"]')"/>
                   <xsl:variable name="musician" select=
                                 "document($m_query)//jazzMusician"/>
                   <!-- Create link to jazz musician web page -->
                   <a href=
                       "{concat($m_query,$sheet,'jazzMusician.xsl')}">
                     <img border="0"
                          src="{concat($ency,'/images/arrow.gif')}"/>
                   </a>
                   <xsl:text> </xsl:text>
                   <xsl:value-of select="$musician/name/first"/>
                   <xsl:text> </xsl:text>
                   <xsl:value-of select="$musician/name/middle"/>
                   <xsl:value-of select="$musician/name/last"/>
                   <br/>
                 </xsl:for-each>
                 </p>
               </td>
             </tr>
             <tr>
               <td bgcolor="#FFFFCC">
                 <xsl:if test="publisher">
                   Publisher: <xsl:value-of
                                   select="publisher"/><br/>
                 </xsl:if>
                 ProductNo: <xsl:value-of select="@albumNo"/>
               </td>
             </tr>
           </table>
         </td>
       </tr>
       <tr>
         <td><h3><br/>
           <font color="#C0C0C0">Tracks</font></h3>
           <table width="100%">
             <xsl:for-each select="track">
               <tr>
                 <td bgcolor="#FFFFCC">
                   <!-- Print track number -->
                   <xsl:number value="position()" format="1-"/>
                   <!-- Print character content of track element -->
                   <xsl:value-of select="title"/>
                 </td>
                 <td align="Right" bgcolor="#FFFFCC">
                   <xsl:value-of select=
                        "substring-before(substring-after(duration,'T'),'M')"/>:
                   <xsl:value-of select=
                        "substring-before(substring-after(duration,'M'),'S')"/>
                 </td>
               </tr>
             </xsl:for-each>
         </table>
       </td>
     </tr>
     <!-- Perform the join for the reviews -->
     <xsl:variable name="r_query" select=
       "concat($query,'review[album/@albumNo="',@albumNo,'"]')"/>
     <xsl:variable name="reviews" select="document($r_query)//review"/>
     <xsl:if test="$reviews">
     <tr>
       <td><h3><br/>
         <font color="#C0C0C0">Reviews</font></h3>
         <table width="100%">
           <xsl:for-each select="$reviews">
             <tr>
               <td bgcolor="#FFFFCC">
                 <!-- Create link to review web page -->
                 <a href="{@URL}">
                   <img border="0"
                      src="{concat($ency,'/images/arrow.gif')}"/>
                 </a>
     		<!-- Perform the join for the critic -->
     		<xsl:variable name="cr_query" select=
       			"concat($query,'critic[@ID="',critic/@ID,'"]')"/>
    		<xsl:variable name="critic" select="document($cr_query)//critic"/>
		<xsl:value-of select="$critic/name/first"/>
                <xsl:value-of select="$critic/name/last"/> -
                <xsl:call-template name="format-date">
                   <xsl:with-param name="date" select="pubDate"/>
                </xsl:call-template>
               </td>
             </tr>
           </xsl:for-each>
         </table>
       </td>
     </tr>
     </xsl:if>
   </table><br/>
   </xsl:for-each>
  </body>
</html>
</xsl:template>
<!-- Date formatting -->
<xsl:template name="format-date">
  <xsl:param name="date"/>
  <!-- Get month and convert into name -->
  <xsl:variable name="month" select="substring($date,6,2)"/>
  <xsl:choose>
    <xsl:when test="$month=1">January</xsl:when>
    <xsl:when test="$month=2">February</xsl:when>
    <xsl:when test="$month=3">March</xsl:when>
    <xsl:when test="$month=4">April</xsl:when>
    <xsl:when test="$month=5">May</xsl:when>
    <xsl:when test="$month=6">June</xsl:when>
    <xsl:when test="$month=7">July</xsl:when>
    <xsl:when test="$month=8">August</xsl:when>
    <xsl:when test="$month=9">September</xsl:when>
    <xsl:when test="$month=10">October</xsl:when>
    <xsl:when test="$month=11">November</xsl:when>
    <xsl:otherwise>December</xsl:otherwise>
  </xsl:choose>
  <xsl:text> </xsl:text>
  <!-- Get day -->
  <xsl:value-of select="substring($date,9,2)"/>,
  <!-- Get year -->
  <xsl:value-of select="substring($date,1,4)"/>
</xsl:template>
</xsl:stylesheet>

In this example we see also some advanced formatting. Because XSLT was designed before XML Schema, it is not aware of XML Schema's built-in datatypes. Consequently, there are no easy-to-use formatting routines for these datatypes. This means that if we want to display, for example, a date in any format other than the standard ISO format, we have to write the necessary formatting routine ourselves. This is done in the template format-date.