Variable scope in XSLT - xslt

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

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.

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.

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>

Related

My XSLT Variable doesn't load in template

So I'm trying to create a variable and then later on use this in my template.
This is the code I have now:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="items">
<xsl:call-template name="item" />
</xsl:template>
<xsl:template name="item">
<xsl:for-each select="item">
<xsl:variable name="currentItemTheme">
test variable
</xsl:variable>
<xsl:if test="title = name">
Showvariable: {$currentItemTheme} <br/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I have no idea why it doesn't work. My file does output the "Showvariable: ", but it just doesn't show the test values. So the for-each loops and template aren't the problem.
Is there something I'm missing? Does someone know how I get this to work?
I think you want <xsl:value-of select="$currentItemTheme"/> instead of {$currentItemTheme}, unless you are mixing the XSLT code with some other language that provides the {$currentItemTheme} syntax.
<xsl:copy-of select="$currentItemTheme"/> is another option if you build nodes in your variable and want to output them instead of just the string value as a text node.

xslt - adding Lp. to node

I'm new in xslt, so I've some problems with adding Lp to my transformation.
This's my simple xml data:
<booking>
<bookingID>ww1</bookingID>
<voucherNumber>R-108</voucherNumber>
</booking>
<booking>
<bookingID>ww2</bookingID>
<voucherNumber>R-108</voucherNumber>
</booking>
<booking>
<bookingID>ww3</bookingID>
<voucherNumber>R-108</voucherNumber>
</booking>
<booking>
<bookingID>ww4</bookingID>
<voucherNumber>R-109</voucherNumber>
</booking>
<booking>
<bookingID>ww5</bookingID>
<voucherNumber>R-109</voucherNumber>
</booking>
<booking>
<bookingID>ww6</bookingID>
<voucherNumber>R-110</voucherNumber>
</booking>
The key is voucherNumber, i need to add Lp for the same voucherNumber
I'need output text file to look like this:
ID;VN,LP
ww1;108;1
ww2;108;2
ww3;108;3
ww4;109;1
ww5;109;2
ww6;110;1
I add the key on voucherNumber
<xsl:key name="x" match="booking" use="voucherNumber"/>
in for-each statement I've add this code: it's adding me on the last position (i know that i can change this for another position) the number of count my items for the same voucherNumber, but how i can add number Lp for the other items?
<xsl:choose>
<xsl:when test="generate-id(.) =generate-id(key('x',voucherNumber)[last()])">
<xsl:value-of select="count(key('x',voucherNumber)) "/>
</xsl:when>
<xsl:otherwise>
-- need LP for other items --
</xsl:otherwise>
</xsl:choose>
I can use only version 1.0 of xslt stylesheet.
Thank you for your advice
Best regards
It looks like you are trying to use Muenchian Grouping here, but what you probably should do is start off by selected the booking elements with the first occurrence of each distinct voucherNumber
<xsl:for-each select="booking[generate-id() = generate-id(key('x',voucherNumber)[1])]">
Then, you have a nested xsl:for-each where you get all the booking elements within that group (i.e. the booking elements with the same voucherNumber)
<xsl:for-each select="key('x', voucherNumber)">
Then, within this next xsl:for-each you can use the position() function to get the count of the record within that specific group
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />
<xsl:key name="x" match="booking" use="voucherNumber"/>
<xsl:template match="/*">
<xsl:for-each select="booking[generate-id() =generate-id(key('x',voucherNumber)[1])]">
<xsl:for-each select="key('x', voucherNumber)">
<xsl:value-of select="bookingID" />
<xsl:text>,</xsl:text>
<xsl:value-of select="substring-after(voucherNumber, '-')" />
<xsl:text>,</xsl:text>
<xsl:value-of select="position()" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note, this assumed your actual XML is well-formed and there is a single root element containing all your booking elements.
I have no idea what "Lp" means. Assuming you want to number the bookings sequentially, restarting on voucherNumber, try something like:
-- Edit --
The proper solution here would be to use <xsl:number> to number the nodes. However, since I could not find a single combination of attributes that would work the same way with all XSLT 1.0 processors, I have resorted to the following hack:
<xsl:key name="booking-by-voucherNumber" match="booking" use="voucherNumber"/>
<xsl:template match="/root">
<xsl:for-each select="booking">
<!-- get id and voucher number -->
<xsl:variable name="id" select="generate-id()" />
<xsl:for-each select="key('booking-by-voucherNumber', voucherNumber)">
<xsl:if test="generate-id()=$id">
<xsl:value-of select="position()"/>
</xsl:if>
</xsl:for-each>
<!-- new line -->
</xsl:for-each>
</xsl:template>

xsl : Incrementing a global variable

I know the fact that variables in XSL are immutable. But in my scenario, I want to increment a global variable and use the value later. Is there any other way by which this can be done? I have added enough comments in the code to make my question more clear. Please note I am using XSLT 1.0
<xsl:variable name="counter">0</xsl:variable>
<xsl:template match="/">
<xsl:for-each select="Condition_ABC">
<xsl:variable name="returnValue"> <!--returnValue is the return value from the named template GetValue -->
<xsl:call-template name="GetValue"/>
</xsl:variable>
<xsl:if test="not($returnValue= '')">
<!-- If the returned value is not null
Here I want to add the increment logic, something like this
$counter = $counter+ 1 -->
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="some_pattern">
<Attribute>
<!-- the final 'counter' value from above xsl block needs to be outputted here -->
<xsl:value-of select="$counter"/>
</Attribute>
</xsl:template>

Conditional Sibling Value

I need to write a XSLT to get the value of the nearest dictionary value to a give node. For example, my structure could be as below
<rootnode>
<rootcontainer>
<dictionary>
<key1> value /<key1>
</dictionary>
<pages>
<page1>
<!--xslt goes here-->
</page1>
</pages>
</rootcontainer>
<dictionary>
<key1>
independent value
</key1>
<key2>
value 2
</key2>
</dictionary>
</rootnode>
I want to create variables $key1 and $key2 inside page1. The value of $key1 will be "value" and the value of $key2 will be "value 2". If rootcontainer\dictionary\key1 doesn't exist, the value of $key1 will be "independent value".
I hope this makes sense.
Here is a compact way to define the required variables:
<xsl:variable name="vKey1" select=
"(/*/rootcontainer/dictionary/key1
|
/*/dictionary/key1
)
[1]
"/>
<xsl:variable name="vKey2" select=
"(/*/rootcontainer/dictionary/key2
|
/*/dictionary/key2
)
[1]
"/>
When wrapped in a simple xslt stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="vKey1" select=
"(/*/rootcontainer/dictionary/key1
|
/*/dictionary/key1
)
[1]
"/>
<xsl:variable name="vKey2" select=
"(/*/rootcontainer/dictionary/key2
|
/*/dictionary/key2
)
[1]
"/>
<xsl:template match="/">
Key1: <xsl:value-of select="$vKey1"/>
Key2: <xsl:value-of select="$vKey2"/>
</xsl:template>
</xsl:stylesheet>
and applied on the provided XML document (corrected, as it was severely malformed):
<rootnode>
<rootcontainer>
<dictionary>
<key1> value </key1>
</dictionary>
<pages>
<page1> </page1>
</pages>
</rootcontainer>
<dictionary>
<key1> independent value </key1>
<key2> value 2 </key2>
</dictionary>
</rootnode>
the wanted, correct result is produced:
Key1: value
Key2: value 2
Explanation:
The expression:
(/*/rootcontainer/dictionary/key1
|
/*/dictionary/key1
)
[1]
means:
Take the nodeset of (potentially) the two elements and from them take the first one in document order.
Because, the second of these two elements comes later in document order, it will be the first (and selected), only when the first of the two XPath expressions surrounding the union ( |) operator, doesn't select any element.
I'm not sure I understand the question but you can assign a conditional value to a variable in two ways:
<xsl:choose>
<xsl:when test="your test condition">
<xsl:variable name="key1" select="your value">
</xsl:when>
<xsl:otherwise>
<xsl:variable name="key1" select="your alternative value">
</xsl:otherwise>
</xsl:choose>
Or more succintly:
<xsl:variable name="key1" select="if(your test condition) then your value else your alternative value;"/>
Update: Thanks for the update of the question. I'll give it a go now.
<xsl:template match="page1">
<xsl:choose>
<xsl:when test="../../preceding-sibling:dictionary[1]/key1">
<xsl:variable name="key1" select="../../preceding-sibling:dictionary[1]/key1">
</xsl:when>
<xsl:otherwise>
<xsl:variable name="key1" select="../../../following-sibling:dictionary[1]/key1">
</xsl:otherwise>
</xsl:choose>
</xsl:template>
So the value of $key1 will be the <key1> node in the preceding dictionary if there is one, and the <key1> node in the following dictionary if there isn't. Is that correct?
(You can use the if/then/else structure too if you want to, but I used xsl:choose because it's probably easier to read.)

Is xsl:sequence always non-empty?

I don't understand output from this stylesheet:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="root/sub"/>
</xsl:template>
<xsl:template match="sub">
<xsl:variable name="seq">
<xsl:sequence select="*" />
</xsl:variable>
<xsl:message>
<xsl:value-of select="#id" />
<xsl:text>: </xsl:text>
<xsl:value-of select="count($seq)" />
</xsl:message>
</xsl:template>
</xsl:stylesheet>
when applied to following XML:
<root>
<sub id="empty" />
<sub id="one"><one/></sub>
<sub id="two"><one/><one/></sub>
<sub id="three"><one/><one/><one/></sub>
</root>
Output, written by xsl:message element, is:
empty: 1
one: 1
two: 1
three: 1
I expected this one instead:
empty: 0
one: 1
two: 2
three: 3
Why does count($seq) always return 1 in this case? How would you change variable definition, so that I can later test it for emptiness? (Simple <xsl:variable name='seq' select='*' /> would return expected answer, but is not an option ... I want to change between variable in this template, and test it for emptiness later).
Let me try to answer the "why" part of your question.
If you write the following statement:
<xsl:variable name="x" select="*" />
the variable $x contains the sequence of the child nodes of the current node. There's no implicit parent node in $x, because you use select. Now consider the following:
<xsl:variable name="x">
<content />
<content />
</xsl:variable>
where variable $x contains a sequence of one node: the parent node of content. Here, count($x) will always give you 1. To get the amount of content elements, you need to count the children of the $x implicit root node: count($x/content).
As a rule of thumb, you can remember that: if xsl:variable is itself an empty element with a select attribute, the result set will be assigned to the variable. If you use xsl:variable without the select attribute, you always create an implicit parent with the variable, and the contents of the variable are the children underneath it.
The same applies for your example with xsl:sequence as a child to xsl:variable. By having a non-empty xsl:variable you create an implicit parent for the sequence, which is why you always get 1 if you count the variable itself.
If you need both: a bundle of statements inside xsl:variable, but the behavior of the select attribute, you can workaround this by using the following:
<xsl:variable name="x" select="$y/*" />
<xsl:variable name="y">
<xsl:sequence select="foo" />
</xsl:variable>
which will now yield the expected amount for count($x), but whether or not that's really beneficial or makes your code clearer is arguable.
It works as you might expect if you either change the variable declaration to:
<xsl:variable name="seq" select="*"/>
or declare the type of the variable using 'as' attribute:
<xsl:variable name="seq" as="item()*">
<xsl:sequence select="*" />
</xsl:variable>
Not specifying any type information often leeds to surprising results in XSLT 2.0. If you are using Saxon, you can output how Saxon interprets the stylesheet using the explain extension attribute:
<xsl:template match="sub" saxon:explain="yes" xmlns:saxon="http://saxon.sf.net/">
<xsl:variable name="seq">
<xsl:sequence select="*" />
</xsl:variable>
<xsl:message>
<xsl:value-of select="#id" />
<xsl:text>: </xsl:text>
<xsl:value-of select="count($seq)" />
</xsl:message>
</xsl:template>
As you can see, Saxon constructs a document-node out of the sequence:
Optimized expression tree for template at line 6 in :
let $seq[refCount=1] as document-node() :=
document-constructor
child::element()
return
message
You are selecting the sub nodes, and then counting each node - so it'll always be 1. You need to count the children, for example:
<xsl:value-of select="count($seq/*)" />
will give you the output you're expecting.