[xsl] having a template remember not to call itself again

Subject: [xsl] having a template remember not to call itself again
From: "Chris Papademetrious christopher.papademetrious@xxxxxxxxxxxx" <xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx>
Date: Sun, 5 Mar 2023 16:20:28 -0000
Hi everyone,

I have a stylesheet with many templates that must all chain together and play
nice with each other. So I write them in the following form:

  <xsl:template match="CONDITION1_HERE">

    <!-- apply this template's processing first -->
    <xsl:variable name="result" as="element()">
      ...PROCESSING1_HERE...
    </xsl:variable>

    <!-- apply subsequent self-or-children templates last -->
    <xsl:apply-templates select="$result"/>
  </xsl:template>

When a template applies its processing first then calls other templates last,
I will call it "tail-call" template chaining (although I don't know the
correct term).

For "tail-call" chaining to work, PROCESSING1_HERE must transform the content
so that CONDITION1_HERE is not met again (or at least not met in a way that
loops infinitely).

But, what if PROCESSING1_HERE is very complex (nested moded templates,
recursion, etc.) and sometimes CONDITION1_HERE will match after this template
was previously applied, and there is no practical way to embed the complexity
of predetermining PROCESSING1_HERE's failure to remove the condition into
CONDITION1_HERE's match expression?

This could be avoided by using "head-call" chaining:

  <xsl:template match="CONDITION2_HERE">

    <!-- apply subsequent self-or-children templates first -->
    <xsl:variable name="result" as="node()*">
      <xsl:next-match/>
    </xsl:variable>

    <!-- apply this template's processing last -->
    ...PROCESSING2_HERE...
  </xsl:template>

But now, all bets are off on what PROCESSING2_HERE will encounter. Maybe the
result will have multiple elements, or be filtered out to zero elements, or
might have text() nodes interspersed due to reformatting and styling
templates. Maybe <xsl:next-match/> modified the content such that
CONDITION2_HERE isn't even matched any more. PROCESSING2_HERE must handle a
much wider range of possible input, and the more templates that exist in the
stylesheet, the more varied the input from <xsl:next-match/> might be. (I
actually had all my templates written as "head-call" chaining, and I am
converting them to "tail-call" chaining due to such issues.)

So now I'm back to "tail-call" chaining, and figuring out how to get a
template to not call itself when it fails to remove the condition triggering
the match. I tried setting a tunnelling variable that would give a heads-up to
the template not calling itself again:

  <xsl:template match="CONDITION1_HERE[not($CONDITION1_CALLED)]">
    <xsl:param name="CONDITION1_CALLED" as="xs:boolean" select="false()"
tunnel="yes"/>

    <!-- apply this template's processing -->
    <xsl:variable name="result" as="element()">
      ...PROCESSING1_HERE...
    </xsl:variable>

    <!-- apply subsequent self-or-children templates -->
    <xsl:apply-templates select="$result">
      <xsl:with-param name="CONDITION1_CALLED" as="xs:boolean" select="true()"
tunnel="yes"/>
    </xsl:apply-templates>
  </xsl:template>

but the template's $CONDITION1_CALLED parameter is out-of-scope in its match
expression.

So now the only solution I can think of is to put some kind of temporary
marker attribute in the matching element, then have a final document-down
cleanup pass to remove the markers. And if multiple templates need markers,
I'll need to clean them all up. Icky.

Is there a more elegant way to handle this that am missing?

Thanks as always for your collective wisdom, and thanks for making it this
far!


  *   Chris

-----
Chris Papademetrious
Tech Writer, Implementation Group
(610) 628-9718 home office
(570) 460-6078 cell

Current Thread