Re: testing for last node in a list

Subject: Re: testing for last node in a list
From: Jeni Tennison <Jeni.Tennison@xxxxxxxxxxxxxxxx>
Date: Wed, 24 May 2000 18:18:28 +0100
Ann Marie,

>Here's another tough one.  

I like a challenge ;)

>Now I need to output the @NAME of each ancestor, except the last one, with
the href tag,
>except for the last one. The last ancestor doesn't need a href tag because
it is the name of the current 
>matched class. 

OK, so taking an original of:

<?xml version="1.0" ?>
<?xml:stylesheet type="text/xsl" href="test.xsl"?>
<SCHEMA>
  <CLASS NAME="animal" />
  <CLASS NAME="mammal" SUPERCLASS="animal" />
  <CLASS NAME="canine" SUPERCLASS="mammal" />
  <CLASS NAME="dog" SUPERCLASS="canine" />
  <CLASS NAME="wolf" SUPERCLASS="canine" />
  <CLASS NAME="feline" SUPERCLASS="mammal" />
  <CLASS NAME="cat" SUPERCLASS="feline" />
</SCHEMA>

then for the wolf, you now want to give a hierarchy something like:

...
<a href="animal.html">animal</a>
|
<a href="mammal.html">mammal</a>
|
<a href="canine.html">canine</a>
|
wolf
...

[deliberately simplified]

>Using your approach, I ask myself what do I know about node X.  In this
case, the @NAME value of Node X 
>always equals the @NAME value of the matched node when the template was
called.  

Or to be more precise, the @NAME value of the matched node when the
template was *originally* called (i.e. from the <xsl:apply-templates
select="." mode="hierarchy" /> within the CLASS-matching template).

Unfortunately, 'you can't get there from here' - if I *only* know about
node X, there is no way of knowing whether it was the one 'originally'
selected or not (though see Caveat 2 below).  Within a template the only
things you know about are where you are, what the source document looks
like and the values of any globally-defined parameters or variables -
nothing about what previous processing has done to you.

>So, I tried to define a variable to store the name of the matched class.
Then I could compare the name 
>of the matched class to the name of the current context node.  But the
value of the variable is 
>automatically updated each time the template is called.  

Right, because the variable defining the name of the class is evaluated
each time the template is called with respect to the current node, which
changes each time the template is called.

Caveat 1: There are lots of approaches that you could take to getting the
output you want from your source document.  Your requirements are gradually
shifting (which is no bad thing - taking an incremental approach can be
really helpful, if not essential), and it may be that at some point your
requirements have changed so much that you want to rethink the approach
you're taking.  I don't think you're quite there yet, but it's worth
staying open minded.

Caveat 2: I've assumed (in putting |s in your hierarchy) that it's easy,
given a node X, to tell whether it's at the *top* of the hierarchy, but
impossible to tell whether it's at the *bottom* of the hierarchy.  This
rests on the assumption that you are constructing hierarchies for classes
that have subclasses as well as for those classes at the bottom of the
hierarchy (so all the classes in the example above rather than just dog,
wolf & cat).  If I'm wrong in this assumption let me know because a
different approach would take advantage of that.

OK, so here's one possible approach.

You want the hierarchy-generating template to know whether it is generating
output for the class at the bottom of the hierarchy, or one part of the way
up.  When need to know something and you can't work it out, you *have* to
be told.  The way you tell a template things is by passing it parameters.

Let's call the parameter 'bottomNode' and make it either 'true' (if you're
at the bottom) or 'false' (if you're not).  (We could equally make a
parameter called 'matchedNode' and give it the value of the name of the
originally matched node - as you said, within the template we know the
@NAME of the current node, and we could test it against that - but actually
this is all we need for now.)  We define the parameter just like a
variable, but using the xsl:param element:

<xsl:param name="bottomNode" />

Now, we could leave it like that and make sure we set it each time we apply
the template, but most of the time that we're calling the template, we're
not calling it on the bottomNode, so we may as well default it to false:

<xsl:param name="bottomNode">false</xsl:param>

Now we've defined that parameter, the template knows whether it's at the
bottom of the hierarchy or not and we can test that to decide what to
output.  Rather than doing two tests (one to see whether we're at the top
and only adding a | above if not, and one to see if we're at the bottom and
only putting in a link if not), we may as well use this new information to
test whether to add a | *below* as well.  So:

<xsl:template match="CLASS" mode="hierarchy">
  <xsl:param name="bottomNode">false</xsl:param>
  <!-- applies hierarchy template to the parent of the current class -->
  <xsl:apply-templates select="key('classes', @SUPERCLASS)" mode="hierarchy"/>
  <!-- outputs details about the current class -->
  <xsl:choose>
    <xsl:when test="$bottomNode = 'false'">
      <br data="{@NAME} -- {@SUPERCLASS}">
        <a href="{@NAME}.html">
          <xsl:value-of select="@NAME"/>
        </a>
      </br>
      <xsl:text>|</xsl:text>
    </xsl:when>
    <xsl:otherwise>
      <br data="{@NAME} -- {@SUPERCLASS}">
        <xsl:value-of select="@NAME"/>
      </br>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

OK, so the template is using the parameter as we want; we just need to make
sure that it's being *passed* the parameter as we want.  The hierarchy
template is being applied twice:

1. within the template generating the basic information for the class
2. recursively within the hierarchy template

Whenever the hierarchy template is applied within the template generating
the basic information for the class, it is being applied to the class at
the bottom of the hierarchy.  So within that xsl:apply-templates, we want
to make sure that the parameter is passed a value of 'true', which we do
using an xsl:with-param element:

<xsl:template match="CLASS">
  ...
  <xsl:apply-templates select="." mode="hierarchy">
    <xsl:with-param name="bottomNode">true</xsl:with-param>
  </xsl:apply-templates>
  ...
</xsl:template match="CLASS">

Whenever the hierarchy template is applied recursively within itself, it's
being applied to something that isn't at the bottom of the hierarchy.  So
it needs to have $bottomNode = 'false'.  Fortunately, we made it 'false' by
default, so there's no need to use an xsl:with-param within that
xsl:apply-templates element (wasn't that great forward thinking on our
part! ;)

I've tested this approach and it works correctly in SAXON.

Cheers,

Jeni

Dr Jeni Tennison
Epistemics Ltd, Strelley Hall, Nottingham, NG8 6PE
Telephone 0115 9061301 ? Fax 0115 9061304 ? Email
jeni.tennison@xxxxxxxxxxxxxxxx



 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread