Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In XSLT, can I run a template only once

Tags:

xslt

I have a block of XSLT that may be applied several times throughout a transform. But I want to it actually run only the first time it is applied, it shoul be skipped all subsiquent times. How do I do this?

As an example, this is the sort of thing I want to do: In the stylesheet I define a global variable:

<xsl:variable name="run_once" select="0"/>

Then I have a template which is called several times:

<xsl:template name="some_template">
    <xsl:if test="$run_once != 1">
        <xsl:variable name="run_once" select="1"/>
        <xsl:text>THIS TEXT SHOULD APPEAR ONLY ONCE</xsl:text>
    </xsl:if>
</xsl:template>

This does not work of course, because variables cannot be changed, only overloaded. So, once the some_template exits $run_once is once again 0 and the text is applied every time the template is called. Is there some type of ifdef functionality or other global object I can set?

If you are interested in why I want to do this, below is a more detailed explanation of my problem and the solution I used:

  • My input is data in raw XML, my output is a report in WordML format.
  • In the input I have a series of nodes (named theNode). Some, but not all, of these nodes need to be displayed in the output. The node should be displayed only if the XPATH hairyLogic is true (hairyLogic is obviously long and complex).
  • theNode's also have a type (stored in a sub-node). In the input, all theNode's of the same type will always be grouped together. In the output, all theNode's of the same type should be grouped under a specific heading for that type (there should be only one heading for each type).

This is the solution I ended up using:

...
<xsl:apply-templates select="theNode[hairyLogic]"/>
...

<xsl:template match="theNode">
    <xsl:if test="count(preceding-sibling::theNode[type = current()/type and hairyLogic])=0">
        <xsl:choose>
            <xsl:when test="type = 'TYPE1a' or type = 'TYPE1b'">
                <xsl:call-template name="TYPE1Heading"/>
            </xsl:when>
            <xsl:when test="type = 'TYPE2'">
                <xsl:call-template name="TYPE2Heading"/>
            </xsl:when>
        </xsl:choose>
    </xsl:if>
    ...
</xsl:template>

I chose to use named templates for the headings because they contain basic WordML that does not depend on any data in the input XML.

I don't like this solution because hairyLogic is repeated and the if statement is convoluted and hard to read. Maybe you have a better solution that doesn't require mutable variables?

like image 856
oillio Avatar asked Aug 27 '09 22:08

oillio


2 Answers

I'd suggest using match templates over named templates because it's more, well, what's the XSLT equivalent of pythonic? Xslt-y? I think you'll find that you can solve problems easier in that processing methodology.

If you insist on using named templates because of some unknown requirement, you may find that if you refactor your logic to make it easy to detect the first instance, you'll simplify your logic altogether.

Can you clarify as to why you can't detect when the the first instance is needed? We can probably help craft an xpath expression that would let you do want you want to. E.g.

<xsl:template name="some_template">
    <xsl:variable name="EXPRESSION" select=".[somelogic='true']"/>
    <xsl:if test="$EXPRESSION">
        <xsl:text>THIS TEXT SHOULD APPEAR ONLY ONCE</xsl:text>
    </xsl:if>
</xsl:template>

Since XSLT is deterministic - an effect of being completely functional as Greg put it - (unless you're doing weird extension stuff) you can decide when the first time is appropriate by applying logic to the input. Also, you have access to the context node in the template so you know where it's being called from.

like image 80
Chris Scott Avatar answered Oct 22 '22 09:10

Chris Scott


Because XSLT is a purely functional language, there are no global variables that you can set.

You will instead have to choose the circumstances under which you call your some_template template. If you only want to call it once, then make only one call to it.

like image 2
Greg Hewgill Avatar answered Oct 22 '22 09:10

Greg Hewgill