0

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>
Joshua Hedges
  • 307
  • 4
  • 16
  • What is `$url`, how does the input document look, which is the result you want in terms of mapping the input to the output? – Martin Honnen Jan 17 '18 at 14:00
  • the $url is just a url from the document. It looks like it might seem weird but it's part of the system I'm working in. A match on "/" is basically a match on the root node. I need to block documents that contain specific values in their url. I can get it to work on "999" but not "111" because I can't iterate though the whole list of excludes. I will have something like 200-400 exclude nodes. – Joshua Hedges Jan 17 '18 at 14:41
  • Btw this all has to be in XSLT 1.0 – Joshua Hedges Jan 17 '18 at 19:04

3 Answers3

0

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.

jdk
  • 111
  • 5
0

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.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • You're right, I think. I'm still not able to get the code to process correctly though. the document would look something like this Blah blah. The above code filters out everything regarless of a match when applied as Keep in mind that $url is part of the system. I know that is infact file://111 – Joshua Hedges Jan 17 '18 at 18:44
  • I have edited the answer and provided links to XSLT 1 fiddles with my suggestion and the parameter `url` preset to different values and I think they do what you want. So somehow your environment must be different. – Martin Honnen Jan 17 '18 at 19:39
  • Wow that solution is perfect. I have to get it to work in my system but you proved that I don't need iteration. Thanks! – Joshua Hedges Jan 17 '18 at 20:02
0

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.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164