Re: [xsl] Re: lookup-table thoughts (was Re: matching multiple times, outputting once?

Subject: Re: [xsl] Re: lookup-table thoughts (was Re: matching multiple times, outputting once?
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Wed, 7 Nov 2001 09:50:59 +0000
Hi Tom,

> Still, I'd like to propose a variation on the Kay/Novatchev
> solutions. My version is longer, but I think that I think that
> coming up with it requires less original thought than either of
> theirs, because rather than writing a specific recursion I'm
> editting a general version. For instance, I'm going to keep the bold
> -> b, italic -> i, underline -> u mapping in a data table, so even
> though it requires several lines it requires no thought, 'cos I can
> start out a stylesheet like so, just editting the data from my
> backwards-solution post or from any similar lookup-table-based
> stylesheet:

You seem to be quite taken with the idea of using XML to store lookup
tables within a stylesheet (and rightly so :) Note that this is going
to become even easier with XSLT 2.0 when you will be able to declare a
variable holding the table:

<xsl:variable name="trans">
  <in>bold</in>       <out>b</out>
  <in>italic</in>     <out>i</out>
  <in>underline</in>  <out>u</out>
</xsl:variable>

and then use that variable directly as $trans without having to access
it as document('')/*/trans:trans. You can try this out already in
processors that implement the XSLT 1.1 WD, such as Saxon and jd.xslt.

Also, it's likely that in XPath 2.0 you'll be able to use functions as
'steps' in a location path, so that you will be able to do things
like:

  $trans/key('transkey', $in)

in order to get the result of using the key on the node tree generated
in the $trans variable. As you can see, this will also make it easier
to access the value from the key without using a named template.

> Now, I want to override that for "emphasis" elements; I want to do
> something to "emphasis" elements that will run through the sequence
> of their attributes, using child nodes as base. I'm thinking of a
> functional-programming pattern
>
>    accum(f,[],base) = base
>    accum(f,[hd]+tail,base) = f(hd,accum(f,tail,base));
>
> (the sum of a sequence S is accum(add,S,0); the product is
> accum(multiply,S,1); the minimum is accum(min,S,+infinity); and so
> on. Whatever. I'm not actually reusing code here, but I am re-using
> a pattern for the umpteenth time since learning it from _The Little
> Lisper_ many long years ago. So I copy in the pattern, matching
> "emphasis" nodes with a call on "accum" and just leaving two blank
> slots for filling in:

The one "problem" with this template (and the same was true of Mike's
and Dimitre's) is that it's not tail recursive - the recursive call is
not the last thing that happens when the template is processed. In
general, it's a good idea to try to make recursive templates tail
recursive so that processors can treat them in the same way as they
would a loop in a procedural programming language - it means they take
up less resources, run faster, and don't hit problems with stack
depth (with processors that recognise them).

Now in this example, there's very little benefit to making the
template tail recursive because it's only ever going to recurse three
times anyway, but you said you wanted education and it's a general
principal that will stand you in good stead for other recursive
templates.

I find it easier to design a tail-recursive template if I imagine the
equivalent loop for what I want to do, which in this case would be:

function accum {
  seq = attributes of emphasis element
  base = value of emphasis element
  while (seq) {
    element name = lookup from first in seq
    base = <element name> + base + </element name>
    attributes = rest of seq
  }
  return base
}

In other words, go through the attributes one at a time and update the
result by adding the relevant element around the existing result. In
the loop, two things get updated, the result and the attributes, so
those need to be the parameters:

<xsl:template name="accum">
  <xsl:param name="seq" select="/.." />
  <xsl:param name="base" />
  ...
</xsl:template>

[Note that I defaulted the $seq parameter to an empty node list
because that's the kind of value it should take. Not giving a default
for the $base parameter is the same as giving it a default of an empty
string.]

In the template, there's obviously the choice between whether to
continue (if there are any nodes in $seq) or not (if there aren't). If
there aren't, then the template needs to return a copy of the $base,
as passed to it from the preceding recursion (it needs to be a copy to
preserve any element structures it might contain):

<xsl:template name="accum">
  <xsl:param name="seq" select="/.." />
  <xsl:param name="base" />
  <xsl:choose>
    <xsl:when test="$seq">
      ...
    </xsl:when>
    <xsl:otherwise>
      <xsl:copy-of select="$base" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Otherwise, it needs to find the element name and create the relevant
element around the existing $base. But this doesn't get returned; this
is the new value for the $base parameter on the next recursion:

<xsl:template name="accum">
  <xsl:param name="seq" select="/.." />
  <xsl:param name="base" />
  <xsl:choose>
    <xsl:when test="$seq">
      <xsl:variable name="hdvalue">
        <xsl:call-template name="lookup-trans">
          <xsl:with-param name="in" select="name($seq[1])"/>
        </xsl:call-template>
      </xsl:variable>
      <xsl:call-template name="accum">
        <xsl:with-param name="seq" select="$seq[position() > 1]" />
        <xsl:with-param name="base">
          <xsl:element name="{$hdvalue}">
            <xsl:copy-of select="$base" />
          </xsl:element>
        </xsl:with-param>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:copy-of select="$base" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Now for the call to the template. The value of the $seq parameter is
the set of attributes. The value for the $base parameter is the result
of applying templates to the content of the emphasis element - this is
the thing that gets copied into the content of whatever elements are
generated:

<xsl:template match="emphasis">
  <xsl:call-template name="accum">
    <xsl:with-param name="seq" select="@*"/>
    <xsl:with-param name="base">
      <xsl:apply-templates />
    </xsl:with-param>
  </xsl:call-template>
</xsl:template>

If the accum template *wasn't* used elsewhere, I'd be tempted to merge
the two templates into a single one that matches the emphasis element
and has the name 'accum'. The default values for the $seq and $base
parameters could then be given the values that are passed in from the
emphasis template:

<xsl:template match="emphasis" name="accum">
  <xsl:param name="seq" select="@*" />
  <xsl:param name="base">
    <xsl:apply-templates />
  </xsl:param>
  ...
</xsl:template>

Cheers,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/


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


Current Thread