xslt iterate through a whole list or until match

547 views Asked by At

I need to iterate through a list of objects and compare them in xslt. If a match is found I need to stop processing the document. If I get to the end of the list and no match is found then I need to process the document. The problem is xslt variables can't be changed once they are declared. This would be a simple For loop with a true/false variable in other common languages.

     <!--I need to iterate through this list-->
        <xsl:variable name="exclude">
          <exclude block="999" />
          <exclude block="111" />
        </xsl:variable>

        <xsl:template match="@*|node()">
          <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
          </xsl:copy>
        </xsl:template>

<!-- Here I iterate through the docs, but I don't iterate though the objects in the list.The main problem is I don't know how to search the list and make a decision at the end or when I encounter a match. This code only works on the first object "999" -->
        <xsl:template match="/">
          <xsl:if test="not(contains($url, exsl:node-set($exclude)/exclude/@block))">
            <xsl:copy-of select="." />
          </xsl:if>
        </xsl:template>
3

There are 3 answers

3
Martin Honnen On BEST ANSWER

You haven't shown the input document nor how you set up the variable and what exactly you want to check but I don't think you need any iteration, if you want to check that none of the block attribute values is contained in your variable then

<xsl:if test="not(exsl:node-set($exclude)/exclude/@block[contains($url, .)])">

suffices to do that.

I have put three samples up at

  1. http://xsltfiddle.liberty-development.net/3Nqn5Y9
  2. http://xsltfiddle.liberty-development.net/3Nqn5Y9/1
  3. http://xsltfiddle.liberty-development.net/3Nqn5Y9/2

the first two match (parameter url is <xsl:param name="url">file://111</xsl:param> respectively <xsl:param name="url">file://999</xsl:param>) and no output is created, the last doesn't match (<xsl:param name="url">file://888</xsl:param>) and the output is created.

So at least in the context of a normal XSLT 1 setting with the variable or parameter as shown the approach works.

0
jdk On

If you are using XSLT 2.0 you can use tail-end recursion within a template or function, ideally with a primary choose-when-otherwise construction (the when statements providing the termination mechanism, and the otherwise preparing the next loop and calling itself).

If you're using 3.0, try xsl:iterate.

0
Michael Kay On

There are quite a few possible strategies here.

Often, it's possible to write a path expression that selects the items you want to process, and use this in the select attribute of xsl:for-each.

The case where that doesn't work is where the processing of one item (or the decision to terminate) depends on information computed for previous items (and where it's too expensive to repeat the computation each time).

The classic solution in that case is to use head-tail recursion: write a template or function that takes the list of items as a parameter. The template/function must (a) decide whether to continue processing (usually it exits when the input list is empty, but other termination conditions are possible), (b) processes the first ("head") item, and (c) call itself recursively to process the rest of the list (the "tail").

In XSLT 3.0 there's a convenience instruction called xsl:iterate that many people find easier to use that recursion, because it looks more like conventional procedural programming. It's like xsl:for-each except that execution is strictly sequential, and after processing one item you set parameters which are available when processing the next item; and there's an xsl:break instruction for early exit.

Finally, in 3.0 you can use the functional programming approach of a higher-level fold-left or fold-right function. Unless you've come to XSLT from other functional programming languages this probably won't be your first choice. Also, it always processes the whole input sequence: there's no option for early exit.