Navigation with XLink

The approach shown above, i.e. starting with an existing HTML page and constructing a style sheet from that, works fine for small applications. For a large application, however, we would easily lose the overview over all the stylesheets, transformations, hyperlinks and other connections between the various resources.


Defining Navigational Objects

In such cases, it is necessary to adopt a more systematic approach both for the presentation design and for the navigation model. A good basis for such an approach is the conceptual data model. In the chapter From Conceptual Model to Schema::From Model to Schema we had already discussed how XML schemas can be derived from a conceptual model. Here we see how the conceptual model can guide us through web page and navigation design. We use the same multi-namespace model that we developed in the section From Conceptual Model to Schema:: Models and Namespaces

graphics/jazz5.png

The first step is to partition the conceptual model into navigational objects (such as web pages). In many cases the relationship between business objects and navigational objects will not be a 1:1 relationship. There will be cases where a single business object needs to be partitioned into several related navigational objects, simply because of its size. In such a case we should identify one navigational object as the main navigational object for this type of business object. It should be possible to reach the other partitions from this main object via hyperlinks.

In our jazz example we have partitioned the business object jazzMusician into two navigational objects: jazzMusician, which is the main navigational object for this business object type, and instrument, which informs the end user about the instruments played by a particular musician.

In other cases, information from one business object must be augmented with information from other business objects. For example, in the case of our album business object we might want to display collaboration information such as participating artists, time and location of a live performance, etc. along with the album data, as we have already shown in the section Mapping a schema to a web page, i.e. we aggregate data from several business objects into one navigation object.

In addition to the navigational objects that relate to conceptual business objects, we might also offer additional access structures:

  • Indices that collate information from the instances of a given business object type. For example, we might have a web page that contains an alphabetical index of all jazz musicians, and another that contains an index of all styles. The individual index entries would lead to the main navigational objects of the respective business objects.

  • Tables of contents and site maps that provide structured overviews over the web or subsets of it.

  • A synopsis that provides an informal overview of the web.

  • Guided tours that lead the user through the most important areas.

  • Landmarks that act as entry points for sub-areas or for special functionality of a web. These landmarks appear in a consistent form on each web page.

  • Portals that act as entry points for the whole web.

Each of these navigational objects can be represented by a combination of a Tamino query expression and a stylesheet as shown above in the section Using Style Sheets with Tamino.

Defining Navigational Links

Navigational objects are connected via navigational links. Depending on its type, a navigational link may be traversed by the end user either in one direction (unidirectional) or in both directions (bidirectional). Navigational links are based on the links between conceptual objects but, again, there is no 1:1 relationship between a conceptual link and a navigational link. In many cases we want to complement the existing conceptual links with additional access paths that act as shortcuts. A typical example is shown in the diagram above, where we have introduced a shortcut from jazzMusician to album. Shortcuts can be derived from conceptual links by combination. In the diagram above, the shortcut is derived from a combination of two conceptual links: collaboration->jazzMusician and collaboration->album.

In terms of modeling, a navigational model is a view of a conceptual model. A view may only reveal certain aspects of a conceptual model, but it may also introduce new, derived items. In fact, a single conceptual model may have multiple views, for example, to cater for different groups of users. Views are also subject to more frequent changes than conceptual models: the analysis of user behavior often suggests changes to the navigational structures of a web.

In the discipline of implementing hypertext systems (and webs are hypertext systems), this situation has led to the practice of implementing navigational links as first class objects in a separate layer. By removing navigational structures from the presentation layer, the maintenance of both the presentation layer and the navigational layer becomes easier. Changing the design of a web, for example, does not affect the navigation structures, while re-routing navigation paths does not require updating the presentation logic of all web pages.

How would we store such a first class link object in Tamino?

One possibility would be to store a separate document for each link. However, this would cause a high frequency of read accesses to Tamino and would be detrimental to performance. Therefore we combine several links into a linkbase. Linkbases are not our own invention but are a concept that was formulated in the context of XLink. XLink provides also most of the syntactic means that are required to describe independent navigational structures.

Introducing XLink

Conceptually, XLink builds on a network of nodes and arcs. Navigational objects act as nodes, and the arcs specify the pairs of navigational objects between which transitions are possible and the direction in which transitions are allowed. In our scenario, however, we are not really interested in describing navigational transitions between individual navigational objects but rather between sets of these objects. Therefore, we have to extend XLink. In the rest of this section we describe how we do this. At the same time we introduce the basic concepts of XLink.

To define a linkbase we create a linkbase document. Within each linkbase we define one or several extended links. Typically, we would define a separate extended link for each user type:

<linkbase xmlns:xlink="http://www.w3.org/1999/xlink">
  <encyclopediaLinks xlink:type="extended" user="jazzfan">
  ...
  </encyclopediaLinks>
  ...
</linkbase>

XLink can be used to define within each extended link an arbitrary number of nodes and arcs. To describe the nodes (in our case, sets of navigational objects) we use XLink locators. Usually, an XLink locator describes a foreign web resource. In our case, however, we want to describe a virtual navigational object that is generated at runtime from a Tamino query and an XSL stylesheet. Therefore, we extend the definition of the locator element with an xsl:stylesheet attribute:

<musician xlink:type="locator"
  xlink:href=
   "http://localhost/tamino/jazz/encyclopedia?_XQL=e:jazzMusician"
  xsl:stylesheet="xsl:stylesheet/jazzMusician.xsl"
  xlink:label="mus"/>

In this example, the locator points to a set of jazzMusician instances stored in Tamino. The set of virtual navigational objects results from the application of the specified stylesheet to the query result. The final attribute (xlink:label) identifies this locator for the following arc definition:

<collaborationToJazzMusician xlink:type="arc"
  xlink:from="col"
  xlink:to="mus"
  xqlFilter="[e:jazzMusician/@ID='$keyvalue']"/>

This defines a possible transition between two locators which are identified by their xlink:label attributes, as specified in xlink:from and xlink:to. The attribute xlink:title can optionally be used to decorate the resulting hyperlink.

Here, we have extended the standard XLink mechanism with an xqlFilter attribute. This attribute describes how specific instances of the navigational target object are selected. In the filter expression we use $keyvalue as a placeholder for a key value. We see later how this expression can be evaluated within a stylesheet.

The Navigational Model

In the following example, we define a locator for each navigational object that represents a document type such as jazzMusician, style, collaboration, or album. In addition, we define a navigational object instruments that relies on jazzMusician but focuses on the instruments played by this musician. Also, we define locators that represent indexes such as bandIndex and musicianIndex:

<?xml version="1.0"?>
<linkbase
      xmlns:xlink="http://www.w3.org/1999/xlink"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns="http://www.softwareag.com/tamino/doc/examples/models/jazz/shop"
      xmlns:e="http://www.softwareag.com/tamino/doc/examples/models/jazz/encyclopedia"
      xmlns:i="http://www.softwareag.com/tamino/doc/examples/models/instruments"
>
<encyclopediaLinks xlink:type="extended" user="jazzfan">
  <!-- Locators -->
  <CDs xlink:type="locator"
    xlink:href="http://localhost/tamino/jazz/shop?_XQL=CD"
    xsl:stylesheet="xsl:stylesheet/cd.xsl"
    xlink:label="cd"/>
  <collaborations xlink:type="locator"
    xlink:href=
      "http://localhost/tamino/jazz/encyclopedia?_XQL=e:collaboration"
    xsl:stylesheet="xsl:stylesheet/collaboration.xsl"
    xlink:label="col"/>
  <bandIndex xlink:type="locator"
    xlink:href=
     "http://localhost/tamino/jazz/encyclopedia?_XQL=e:collaboration
                                              [@type='band']%20sortall%20(./name)"
    xsl:stylesheet="xsl:stylesheet/band-index.xsl"
    xlink:label="bnd-ix"/>
  <musicians xlink:type="locator"
   xlink:href=
     "http://localhost/tamino/jazz/encyclopedia?_XQL=e:jazzMusician"
    xsl:stylesheet="xsl:stylesheet/jazzMusician.xsl"
    xlink:label="mus"/>
  <musicianIndex xlink:type="locator"
    xlink:href=
"http://localhost/tamino/jazz/encyclopedia?_XQL=e:jazzMusician%20sortall20(./@ID)"
    xsl:stylesheet="xsl:stylesheet/jazzMusician-index.xsl"
    xlink:label="mus-ix"/>
  <instruments xlink:type="locator"
    xlink:href=
      "http://localhost/tamino/jazz/encyclopedia?_XQL=e:jazzMusician"
    xsl:stylesheet="xsl:stylesheet/instrument.xsl"
    xlink:label="instr"/>
  <styles
    xlink:type="locator"
    xlink:href="http://localhost/tamino/jazz/encyclopedia?_XQL=e:style"
    xsl:stylesheet="xsl:stylesheet/style.xsl"
    xlink:label="sty"/>
  <reviews
    xlink:type="locator"
    xlink:href="http://localhost/tamino/jazz/encyclopedia?_XQL=e:review"
    xsl:stylesheet="xsl:stylesheet/review.xsl"
    xlink:label="rev"/>
<!-- Arcs -->
  <cdToCollaboration xlink:type="arc"
    xlink:from="cd"
    xlink:to="col"
    xqlFilter="[@albumNo='$keyvalue']"/>
  <collaborationToJazzMusician xlink:type="arc"
    xlink:from="col"
    xlink:to="mus"
    xqlFilter="[e:jazzMusician/@ID='$keyvalue']"/>
  <jazzMusicianToInstrument xlink:type="arc"
    xlink:from="mus"
    xlink:to="instr"
    xqlFilter="[@ID='$keyvalue']"/>
  <jazzMusicianToCDs xlink:type="arc"
    xlink:from="mus"
    xlink:to="cd"
    xqlFilter=
"[@albumNo=xqx.qdoc('encyclopedia/e:collaboration
   [e:jazzMusician/@ID=&quot;$keyvalue&quot;]')/e:result/@albumNo]"/>
  <cdToReview xlink:type="arc"
    xlink:from="cd"
    xlink:to="rev"
    xqlFilter="[@albumNo='$keyvalue']"/>
  <jazzMusicianToReview xlink:type="arc"
    xlink:from="mus"
    xlink:to="rev"
    xqlFilter="[@ID='$keyvalue']"/>
  <ToBandIndex xlink:type="arc"
    xlink:to="bnd-ix"
    xlink:title="Bands"/>
  <ToMusicianIndex xlink:type="arc"
    xlink:to="mus-ix"
    xlink:title="Musicians"/>
</encyclopediaLinks>
</linkbase>

Note that we have used the X-Query operator sortall for the index locators. Because these expressions appear in the query part of a URL, we must express the blank character with the code %20.

Among the arcs, we have also defined two that lead to the index locators. We want to include hyperlinks to these indices on every web page that we generate. Because there is no specific from node, we just omit the xlink:from attribute for these arcs.

The arc jazzMusicianToAlbums describes a derived link. We use the function xqx.qdoc (which we developed in Utilizing Server Extensions:: qdoc) to establish a link from jazzMusician via collaboration to album.

Using the Link Base

To interpret such a link base within an XSL stylesheet, we must first load the linkbase from Tamino. Let us assume that the linkbase has been stored in Tamino under document type linkbase in our collection encyclopedia. Then we can load the relevant extended link with:

<xsl:variable name="linkbase" select=
"document('http://localhost/tamino/jazz/encyclopedia?_XQL=linkbase//encyclopediaLinks
                            [@user=&quot;jazzfan&quot;]')//encyclopediaLinks"/>

To generate individual hyperlinks we use the following template. Because the generation is a bit lengthy, we pack it into a separate template that can be invoked with <call-template name="gen-link"/>.

<!-- Link generation -->
<xsl:template name="gen-link"
      xmlns:xql="http://metalab.unc.edu/xql/"
      xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- selected arc -->
  <xsl:param name="arc"/>
<!-- key value for xql filter expression
     if omitted we do not generate a filter expression -->
  <xsl:param name="keyvalue"/>
<!-- Title: if not defined use title of arc as default -->
  <xsl:param name="title" select="$arc/@xlink:title"/>
<!-- generate link only if arc is present -->
  <xsl:if test="$arc">
<!-- select target locator -->
    <xsl:variable name="to" select=
    "$arc/../*[@xlink:type='locator' and @xlink:label=$arc/@xlink:to]"/>
    <a>
<!-- generate href -->
      <xsl:attribute name="href" >
<!-- this is the target document -->
        <xsl:value-of select="$to/@xlink:href"/>
<!-- this is the filter -->
<!-- generate only if $keyvalue is supplied and a filter expression is present -->
        <xsl:if test="$keyvalue and $arc/@xqlFilter">
          <xsl:value-of select=
                "substring-before($arc/@xqlFilter,'$keyvalue')"/>
<!-- replace placeholder with actual value -->
          <xsl:value-of select="$keyvalue"/>
          <xsl:value-of select=
                "substring-after($arc/@xqlFilter,'$keyvalue')"/>
        </xsl:if>
<!-- this is the stylesheet -->
        <xsl:if test="$to/@xsl:stylesheet">
          <xsl:text>&amp;_xslsrc=</xsl:text>
          <xsl:value-of select="$to/@xsl:stylesheet"/>
        </xsl:if>
      </xsl:attribute>
<!-- decorate with title -->
      <xsl:choose>
        <xsl:when test="$title">
          <xsl:value-of select="$title"/>
        </xsl:when>
        <xsl:otherwise>
<!-- default decoration -->
          <img border="0" src="{concat($ency,'/images/arrow.gif')}"/>
        </xsl:otherwise>
      </xsl:choose>
    </a>
  </xsl:if>
</xsl:template>

This template takes three parameters:

  • arc identifies the arc to be generated. If this arc does not exist in the linkbase, no link is generated.

  • keyvalue is the key value that identifies the particular instance of the navigation object. If this parameter is not specified, no filter expression is generated.

  • title is a locally defined decoration for the link. If this parameter is not specified, the title defined in the arc is used instead. If no title is defined there either, some default decoration is generated.

A typical invocation of this template looks like this:

<xsl:call-template name="gen-link">
  <xsl:with-param name="arc"
                  select="$linkbase/jazzMusicianToAlbums"/>
  <xsl:with-param name="keyvalue" select="@ID"/>
  <xsl:with-param name="title">Albums</xsl:with-param>
</xsl:call-template>

Using this template call, the final stylesheet for our album web page looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
      xmlns:e="http://www.softwareag.com/tamino/doc/examples/models/jazz/encyclopedia"
      xmlns:i="http://www.softwareag.com/tamino/doc/examples/models/instruments"
      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="//e:album">
     <!-- Perform a join for the collaboration -->
     <xsl:variable name="c_query" select=
     "concat($query,'e:collaboration[e:result/@albumNo="',@albumNo,'"]')"/>
     <xsl:variable name="collab"
                   select="document($c_query)//e:collaboration"/>
     <table>
       <tr>
         <td colspan="2" align="right">
           <font color="#FFFFCC">

             <xsl:call-template name="gen-link">
               <xsl:with-param name="arc"
                    select="$linkbase/ToMusicianIndex"/>
             </xsl:call-template>

             <xsl:text> </xsl:text>

             <xsl:call-template name="gen-link">
               <xsl:with-param name="arc"
                    select="$linkbase/ToBandIndex"/>
             </xsl:call-template>

            </font>
         </td>
       </tr>
       <tr>
         <td>
           <table>
             <tr bgcolor="silver">
               <td bgcolor="#FFFFCC">
                 <h2><xsl:value-of select="e: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/e:name"/></p>
                     <p>
                     <!-- call the template for date formatting -->
                     <xsl:call-template name="format-date">
                       <xsl:with-param name="date"
                                 select="$collab/e:performedAt/e:time"/>
                     </xsl:call-template>
                     <br/>
                     <xsl:value-of select=
                                   "$collab/e:performedAt/e:location"/>
                     </p>
                   </xsl:when>
                   <!-- otherwise print the band/project name -->
                   <xsl:otherwise>
                   <h4>

                      <xsl:call-template name="gen-link">
                       <xsl:with-param name="arc"
                            select="$linkbase/albumToCollaboration"/>
                       <xsl:with-param name="keyvalue"
                                       select="@albumNo"/>
                     </xsl:call-template>

                    <xsl:value-of select="$collab/e:name"/>
                   </h4>
                   </xsl:otherwise>
                 </xsl:choose>
                 <p>
                 <!-- Loop over all collaborateurs -->
                 <xsl:for-each select="$collab/e:jazzMusician">
                   <!-- Create link to jazz musician web page -->

                   <xsl:call-template name="gen-link">
                     <xsl:with-param name="arc"
                      select="$linkbase/collaborationToJazzMusician"/>
                     <xsl:with-param name="keyvalue" select="@ID"/>
                   </xsl:call-template>

                   <!-- Perform the join for the jazzMusicians -->
                   <xsl:variable name="m_query" select=
       "concat($query,'e:jazzMusician[@ID="',@ID,'"]')"/>
                   <xsl:variable name="musician" select=
                                 "document($m_query)//e: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/e:name/e:first"/>
                   <xsl:text> </xsl:text>
                   <xsl:value-of select="$musician/e:name/e:middle"/>
                   <xsl:value-of select="$musician/e:name/e:last"/>
                   <br/>
                 </xsl:for-each>
                 </p>
               </td>
             </tr>
             <tr>
               <td bgcolor="#FFFFCC">
                 <xsl:if test="publisher">
                   Publisher: <xsl:value-of
                                   select="e: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="e:track">
               <tr>
                 <td bgcolor="#FFFFCC">
                   <!-- Print track number -->
                   <xsl:number value="position()" format="1-"/>
                   <!-- Print character content of track element -->
                   <xsl:value-of select="e: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[e:album/@albumNo="',@albumNo,'"]')"/>
     <xsl:variable name="reviews" select="document($r_query)//e: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 -->

                <xsl:call-template name="gen-link">
                  <xsl:with-param name="arc"
                   select="$linkbase/albumToReview"/>
                  <xsl:with-param name="keyvalue" select="e:album/@albumNo"/>
                </xsl:call-template>

    		<!-- Perform the join for the critic -->
     		<xsl:variable name="cr_query" select=
       			"concat($query,'e:critic[@ID="',e:critic/@ID,'"]')"/>
    		<xsl:variable name="critic" select="document($cr_query)//e:critic"/>
		<xsl:value-of select="$critic/e:name/e:first"/>
                <xsl:value-of select="$critic/e:name/e:last"/> -
                <xsl:call-template name="format-date">
                   <xsl:with-param name="date" select="e: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>
<!-- Link generation -->
<xsl:template name="gen-link"
      xmlns:xql="http://metalab.unc.edu/xql/"
      xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- selected arc -->
  <xsl:param name="arc"/>
<!-- key value for xql filter expression
     if omitted we do not generate a filter expression -->
  <xsl:param name="keyvalue"/>
<!-- Title: if not defined use title of arc as default -->
  <xsl:param name="title" select="$arc/@xlink:title"/>
<!-- generate link only if arc is present -->
  <xsl:if test="$arc">
<!-- select target locator -->
    <xsl:variable name="to" select=
    "$arc/../*[@xlink:type='locator' and @xlink:label=$arc/@xlink:to]"/>
    <a>
<!-- generate href -->
      <xsl:attribute name="href" >
<!-- this is the target document -->
        <xsl:value-of select="$to/@xlink:href"/>
<!-- this is the filter -->
        <xsl:if test="$keyvalue and $arc/@xqlFilter">
         <xsl:value-of
            select="substring-before($arc/@xqlFilter,'$keyvalue')"/>
         <xsl:value-of select="$keyvalue"/>
         <xsl:value-of
            select="substring-after($arc/@xqlFilter,'$keyvalue')"/>
        </xsl:if>
<!-- this is the stylesheet -->
        <xsl:if test="$to/@xsl:stylesheet">
  <xsl:text>&amp;_xslsrc=</xsl:text>
         <xsl:value-of select="$to/@xsl:stylesheet"/>
        </xsl:if>
      </xsl:attribute>
      <xsl:choose>
        <xsl:when test="$title">
          <xsl:value-of select="$title"/>
        </xsl:when>
        <xsl:otherwise>
          <img border="0" src="{concat($ency,'/images/arrow.gif')}"/>
        </xsl:otherwise>
      </xsl:choose>
    </a>
  </xsl:if>
</xsl:template>
</xsl:stylesheet>