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.
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
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.
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.
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.
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="$keyvalue"]')/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
.
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="jazzfan"]')//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>&_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>&_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>