Re: [xsl] How to avoid applying templates several times to the same descendant

Subject: Re: [xsl] How to avoid applying templates several times to the same descendant
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Tue, 8 May 2001 09:05:46 +0100
Hi Joakim,

> My problem is: How can I select to "execute" only selected nodes,
> when I don't know what level they are at, or what names they have.
> In addition to the "spec" above, there are a bunch of other
> templates that also have to apply to the elements in question.

I think that the answer is 'use modes'.  Moded templates allow you to
apply several different templates to the same nodes in different
circumstances.  I'll show you the solution to the problem that you
gave (thanks for providing, input, output and some XSLT, btw - very
helpful) which will hopefully demonstrate how to use them in this
problem.

The general approach is to apply templates to the top-level ATLAS
element multiple times - once for each of the unique @ATTR values,
each time making a copy of the ATLAS element and its relevant
children. Because I also want to apply templates to the ATLAS element
as a controlling template, I use the 'copy' mode to distinguish
between the normal processing flow and the special 'copying' process.
I use a parameter to pass in the @ATTR value that I'm currently making
a copy for.

I've used the same key as you:

<xsl:key name="UniqueATTR" match="*[@ATTR]" use="@ATTR"/>

The top-level template has to control the process, applying templates
to the document ATLAS element for each of the unique @ATTR values.  I
loop over the unique values, just as you did, but apply templates to
the ATLAS element in copy mode and pass the @ATTR value as a
parameter.

<xsl:template match="ATLAS">
   <xsl:variable name="atlas" select="." />
   <xsl:for-each select="//*[@ATTR]
                            [generate-id() =
                             generate-id(key('UniqueATTR', @ATTR)[1])]">
      <xsl:apply-templates select="$atlas" mode="copy">
         <xsl:with-param name="attr" select="@ATTR" />
      </xsl:apply-templates>
   </xsl:for-each>
</xsl:template>

The next template is applied as a result.  It matches the ATLAS
element in copy mode and accepts the $attr parameter.  It creates a
ATLAS element with the relevant @ATTR value (using literal result
elements rather than xsl:element/xsl:attribute to make things
clearer).  Then it applies templates to *its* children, again in
'copy' mode and still passing the $attr parameter through.

<xsl:template match="ATLAS" mode="copy">
   <xsl:param name="attr" />
   <ATLAS ATTR="{$attr}">
      <xsl:apply-templates mode="copy">
         <xsl:with-param name="attr" select="$attr" />
      </xsl:apply-templates>
   </ATLAS>
</xsl:template>

The final template is applied on these child elements.  Again it's in
copy mode with an $attr parameter.  Within, I test whether the element
I'm looking at either hasn't got an @ATTR attribute or has one with a
value equal to the $attr parameter.  In either of those cases, I make
a copy of it and then move on recursively to the next layer in the
hierarchy.

<xsl:template match="*" mode="copy">
   <xsl:param name="attr" />
   <xsl:if test="not(@ATTR) or @ATTR = $attr">
      <xsl:copy>
         <xsl:copy-of select="@*" />
         <xsl:apply-templates mode="copy">
            <xsl:with-param name="attr" select="$attr" />
         </xsl:apply-templates>
      </xsl:copy>
   </xsl:if>
</xsl:template>

I hope that helps,

Jeni

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



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


Current Thread