7

I am having an issue trying to figure out var scoping on xslt. What I actually want to do it to ignore 'trip' tags that have a repeated 'tourcode'.

Sample XML:

<trip>
 <tourcode>X1</tourcode>
 <result>Budapest</result>
</trip>
<trip>
 <tourcode>X1</tourcode>
 <result>Budapest</result>
</trip>
<trip>
 <tourcode>X1</tourcode>
 <result>Budapest</result>
</trip>
<trip>
 <tourcode>Y1</tourcode>
 <result>london</result>
</trip>
<trip>
 <tourcode>Y1</tourcode>
 <result>london</result>
</trip>
<trip>
 <tourcode>Z1</tourcode>
 <result>Rome</result>
</trip>

XSLT Processor:

<xsl:for-each select="trip">    
    <xsl:if test="not(tourcode = $temp)">
      <xsl:variable name="temp" select="tour"/>
      // Do Something (Print result!)
    </xsl:if>
</xsl:for-each>

Desired Output: Budapest london Rome

Mazzi
  • 939
  • 4
  • 12
  • 30

3 Answers3

24

You can't change variables in XSLT.

You need to think about it more as functional programming instead of procedural, because XSLT is a functional language. Think about the variable scoping in something like this pseudocode:

variable temp = 5
call function other()
print temp

define function other()
  variable temp = 10
  print temp

What do you expect the output to be? It should be 10 5, not 10 10, because the temp inside the function other isn't the same variable as the temp outside that function.

It's the same in XSLT. Variables, once created, cannot be redefined because they are write-once, read-many variables by design.

If you want to make a variable's value defined conditionally, you'll need to define the variable conditionally, like this:

<xsl:variable name="temp">
  <xsl:choose>
    <xsl:when test="not(tourcode = 'a')">
      <xsl:text>b</xsl:text>
    </xsl:when>
    <xsl:otherwise>
      <xsl:text>a</xsl:text>
    </xsl:otherwise>
  </xsl:choose>
</xsl:variable>
<xsl:if test="$temp = 'b'">
  <!-- Do something -->
</xsl:if>

The variable is only defined in one place, but its value is conditional. Now that temp's value is set, it cannot be redefined later. In functional programming, variables are more like read-only parameters in that they can be set but can't be changed later. You must understand this properly in order to use variables in any functional programming language.

Welbog
  • 59,154
  • 9
  • 110
  • 123
  • Sorry mate, you answer was great, however I have change the question a bit to get it closer to what I am after. Thanks – Mazzi Feb 05 '10 at 04:17
  • 5
    @Mazzi: Always write question towards what you want to do (input->desired output), never just towards *how* you think you can do it. In this case: You do not want to change variable values, you want to produce a list of unique values from the input. – Tomalak Feb 05 '10 at 09:49
  • 4
    Someone should change the question, so this answer fits again. It's much better than the question. Questions are overrated anyway... – John Smithers Feb 05 '10 at 11:26
  • Sorry guys for changing the question. Now I got my answer what do you want me to change the question to? – Mazzi Feb 07 '10 at 23:06
  • [I have a larger number of variables and they all depend on the same two tests](https://stackoverflow.com/questions/30122497/xslt-keep-multiple-variables-in-scope-depending-on-a-single-test) - is there a way to test only twice but keep the variables in scope? – n611x007 May 08 '15 at 11:23
8

Desired Output: Budapest london Rome

What you are after is grouping output by city name. There are two common ways to do this in XSLT.

One of them is this:

<xsl:template match="/allTrips">
  <xsl:apply-templates select="trip" />
</xsl:template>

<xsl:template match="trip">
  <!-- test if there is any preceding <trip> with the same <result> -->
  <xsl:if test="not(preceding-sibling::trip[result = current()/result])">
    <!-- if there is not, output the current <result> -->
    <xsl:copy-of select="result" />
  </xsl:if>
</xsl:template>

And the other one is called Muenchian grouping and @Rubens Farias just posted an answer that shows how to do it.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • Thanks mate, even though out of 4 duplicates it still shows 2 but it is getting me closer to what I had in mind. – Mazzi Feb 07 '10 at 23:04
  • @Mazzi: It does? It did not when I tested it with your sample. If you post the actual XML and XSL you work with I am sure I can point out what's wrong. – Tomalak Feb 07 '10 at 23:36
  • @Tomalak: The xsl and xml are bit bulky, I tried to simplify them as much as possible. I don't know if stackoverflow has a send message functionality?! So I can send it to you directly. – Mazzi Feb 08 '10 at 00:39
  • I don't believe that your output is still having duplicates, If you say it is repeating then use this condition – Rookie Programmer Aravind Feb 08 '10 at 08:54
  • @Mazzi: No, there is no messaging functionality. Either you put it into your question, or you upload it to one of those pastebin websites and post a link. – Tomalak Feb 08 '10 at 09:53
  • @infant programmer: Better not guess, it's not worth it when looking at the real code is possible. ;) – Tomalak Feb 08 '10 at 09:56
  • @Tomalak, I know my suggestion isn't worth on condition we expect .. when Mazzi said it doesn't work (with his HUGE XML) .. I assumed his XML might have some kind of the MIXED TAGS (tourcode isn't mapping to result as he has mentioned in exmple) .. what sin in trying .. ;-) – Rookie Programmer Aravind Feb 08 '10 at 10:11
  • lol, I have paste the xsl and its here: http://tinypaste.com/3941f (I have added some comment on the top) and the xml is located here: http://www.intrepidtravel.com/xml/latestock Cheers, – Mazzi Feb 09 '10 at 01:22
  • 1
    @Mazzi: I've uploaded an all-new version of your XSLT to http://tinypaste.com/f3a23. Feel free to ask followup questions and have fun figuring it out. :-) – Tomalak Feb 10 '10 at 12:57
  • Umm I don't know how to say it, but it is not in price order?! lol Is it left for me to figure out? – Mazzi Feb 10 '10 at 22:28
  • @Mazzi: No, it was meant to be in the right order. ;) It was when I tested it, but maybe I made some kind of mistake. I will check again tomorrow. – Tomalak Feb 10 '10 at 22:59
  • 1
    @Mazzi: Oh damn. I've made one small mistake in the ``. The sort expression is incorrect, but only by a tiny bit. It's rather obvious, I trust you find it very quickly. :) – Tomalak Feb 10 '10 at 23:08
  • @Tomalak: Only if I pick you as the best answer lol. Worked like a charm.. ThanQ – Mazzi Feb 11 '10 at 00:01
  • @Mazzi: Yeah, that's the deal. ;) For the record: The correct version is at http://tinypaste.com/d1dc9f – Tomalak Feb 11 '10 at 06:56
4

Try this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:key name="trip" match="trip" use="result" />

  <xsl:template match="/trips">

    <xsl:for-each select="trip[count(. | key('trip', result)[1]) = 1]">
      <xsl:if test="position() != 1">, </xsl:if>
      <xsl:value-of select="result"/>
    </xsl:for-each>

  </xsl:template>
</xsl:stylesheet>
Rubens Farias
  • 57,174
  • 8
  • 131
  • 162