xslt variable scope and its usage - xslt

I am learning xslt and had one question about how can i use xslt variable in diff. for each loop. I know xslt isn't a procedural language so variable declared in for loop cannot be accessed in another loop. But is there any way I can just declare global variable then assign some value in first for loop and use that variable in second for loop?
Any ideas would be highly appreciated.
Thanks

is there any way I can just declare global variable then assign some
value in first for loop and use that variable in second for loop?
The way to assign value to an xsl:variable (of course this is only initialization) from within an xsl:for-each, is to include the xsl:for-each in the body of the variable.
Here is a complete example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:variable name="vMax">
<xsl:for-each select="num">
<xsl:sort data-type="number" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
Values close to the maximum:
<xsl:text/>
<xsl:for-each select="num">
<xsl:if test="not($vMax - . > 3) ">
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document...
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
...it first defines a vMax variable that gets its value from the xsl:for-each contained in its body.
Then the vMax variable is used in the second xsl:for-each to output all numbers that are "close" to the so computed maximum.
The wanted, correct result is produced:
Values close to the maximum:
07
08
09
10
It is also possible to simulate "assigning" a variable with different values by using a recursively called named template and pass the "new value" as parameter to the called template.
Here is an example showing this technique. Here we are calculating the maximum of values, contained in nodes of a node-set. Every time we access the next node in the node-set, the current maximum is compared to this value and if necessary the new maximum becomes the value of the next node. We then call the same template recursively, passing as the value of the current maximum the new maximum:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:call-template name="max">
<xsl:with-param name="pList" select="*"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="max">
<xsl:param name="pMax" select="-99999999"/>
<xsl:param name="pList"/>
<xsl:choose>
<xsl:when test="$pList[1]">
<xsl:variable name="vnewMax" select=
"$pMax * ($pMax >= $pList[1])
+
$pList[1] * not($pMax >= $pList[1])
"/>
<xsl:call-template name="max">
<xsl:with-param name="pMax" select="$vnewMax"/>
<xsl:with-param name="pList" select="$pList[position() > 1]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$pMax"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied to the same XML document (above), the wanted, correct result is produced:
10

xsl:for-each is not a loop in the sense for or foreach loops exist in procedural languages so any question talking about loops is difficult to understand and more difficult to answer.
If you want to use global variables in XSLT you can do so but you would bind a value to the variable where you declare it (i.e. globally), you can't assign a value later on in a for-each as you seem to want to do.

you have written, 'XSL is a procedural lang' .. Well, its not. It is a Declarative Language ..
Variable is assigned along with its declaration, variables don't change!
Usually we follow recursive call for templates using call-template passing params to them .. (this works like recursive function calling with passing arguments in procedural languages)
That is one method to handle counts and conditional looping etc ..
We would be happy to help you incase if you mention the exact scenario with Sample XML, and the output you are expecting out of it :)

Related

XSLT discrepancy with how variable is used

My question is about xsl:variable and the syntax for a predicate in an Xpath. I've boiled down my question to the point where this short XML can help me demonstrate:
<root>
<tabular>
<col halign="left"/>
<col halign="right"/>
<row>
<cell>Some content</cell>
<cell>Some content</cell>
</row>
</tabular>
</root>
In my application, when I am applying a template on a cell, I need to access the #halign of the corresponding col. In doing so, I have encountered a discrepancy between Xpath expressions that I thought should be equivalent. I would like to understand why this happens. To demonstrate, I apply the XSL at the end of this post using XSLT 1.0.
The cell template in my XSLT here is silly but it lays out the discrepancy I don't understand. Basically it repeatedly tries to print the #halign value corresponding to the second cell. First, using the $col variable that has value 2. Then using [position()=$col]. Then using [number($col)]. Then simply using [2], hard coded. Lastly, using a separate $colsel variable that was defined using a #select attribute.
I expect to see:
ancestor::tabular/col[...]/#halign
[2] makes right
[position()=2] makes right
[number(2)] makes right
(hard 2) [2] makes right
(var #select) [2] makes right
but instead I see:
ancestor::tabular/col[...]/#halign
[2] makes left
[position()=2] makes right
[number(2)] makes right
(hard 2) [2] makes right
(var #select) [2] makes right
Is anyone able to offer an explanation for why using [$col] behaves differently?
Here is the XSL:
<xsl:template match="/">
<xsl:apply-templates select="root/tabular"/>
</xsl:template>
<xsl:template match="tabular">
<xsl:apply-templates select="row"/>
</xsl:template>
<xsl:template match="row">
<xsl:apply-templates select="cell"/>
</xsl:template>
<?xml version='1.0'?> <!-- As XML file -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:apply-templates select="root/tabular"/>
</xsl:template>
<xsl:template match="tabular">
<xsl:apply-templates select="row"/>
</xsl:template>
<xsl:template match="row">
<xsl:apply-templates select="cell[2]"/>
</xsl:template>
<xsl:template match="cell[2]">
<xsl:variable name="col">
<xsl:value-of select="2"/>
</xsl:variable>
<xsl:variable name="colsel" select="2"/>
<xsl:text>ancestor::tabular/col[...]/#halign</xsl:text>
<xsl:text>
</xsl:text>
<xsl:text> [</xsl:text>
<xsl:value-of select="$col"/>
<xsl:text>] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[$col]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text> [position()=</xsl:text>
<xsl:value-of select="$col"/>
<xsl:text>] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[position()=$col]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text> [number(</xsl:text>
<xsl:value-of select="$col"/>
<xsl:text>)] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[number($col)]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text>(hard 2) [2] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[2]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text>(var #select) [</xsl:text>
<xsl:value-of select="$colsel"/>
<xsl:text>] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[$colsel]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Let us use a more convenient example:
XML
<root>
<item>first</item>
<item>second</item>
</root>
XSLT 1.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="num" select="2"/>
<xsl:variable name="str" select="string(2)"/>
<xsl:variable name="rtf">2</xsl:variable>
<xsl:template match="/root">
<results>
<num>
<xsl:copy-of select="item[$num]"/>
</num>
<str>
<xsl:copy-of select="item[$str]"/>
</str>
<rtf>
<xsl:copy-of select="item[$rtf]"/>
</rtf>
</results>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<results>
<num>
<item>second</item>
</num>
<str>
<item>first</item>
<item>second</item>
</str>
<rtf>
<item>first</item>
<item>second</item>
</rtf>
</results>
Now you ask why the difference in the results. The answer can be found in the XPath specification that prescribes how a predicate is to be evaluated:
A PredicateExpr is evaluated by evaluating the Expr and converting the
result to a boolean. If the result is a number, the result will be
converted to true if the number is equal to the context position and
will be converted to false otherwise; if the result is not a number,
then the result will be converted as if by a call to the boolean
function.
In the first instance the value of the $num variable is the number 2. Therefore the result of evaluating the expression within the predicate is a number, and the predicate will be true when the number is equal to the context position - which is only true for the item in the second position.
In the second instance, the value of the $str variable is the string "2". Therefore the expression within the predicate does not evaluate to a number and will be converted to boolean by doing:
boolean("2")
which returns true() for all items, regardless of their position.
In the third instance, the value of the $rtf variable is a result tree fragment that contains a text node that consists of the character "2". When placed in a predicate, the outcome will be similar to the previous instance: the result of evaluating the expression is not a number, and converting it to a boolean will produce a value of true(). Note that your:
<xsl:variable name="col">
<xsl:value-of select="2"/>
</xsl:variable>
does exactly the same thing.
Note also that in XSLT 1.0 the xsl:value-of instruction returns the value of the first node in the selected node-set. Therefore, if we change our template to:
<xsl:template match="/root">
<results>
<num>
<xsl:value-of select="item[$num]"/>
</num>
<str>
<xsl:value-of select="item[$str]"/>
</str>
<rtf>
<xsl:value-of select="item[$rtf]"/>
</rtf>
</results>
</xsl:template>
the result will be:
<results>
<num>second</num>
<str>first</str>
<rtf>first</rtf>
</results>
but still both items are selected by item[$str] and by item[$rtf].
Change the variable declaration to:
<xsl:variable name="col" select="2"/>
and it will behave as you expect and select the second col.
You had declared the variable using xsl:value-of: <xsl:value-of select="2"/>, which creates a computed text() node.
When you use that $col variable by itself in a predicate, that string value "2" it is evaluated as true() in the predicate test, rather than if it were a number() and would then be interpreted as short-hand for position() = 2.

edit value of existing variable xpath

I have a variable which I set the value. Thereafter, in a loop, I would like to edit this variable and use it in the next iteration. Is there any way I can edit the value in the variable? Thanks!
<xsl:variable name="numberVariable" select="5">
----loop----
$numberVariable = $numberVariable+2
----End loop----
in a loop, I would like to edit this variable and use it in the next iteration. Is there any way I can edit the value in the variable?
The answer is negative:
XSLT is a functional language and this, among other things means that a variable's value, once defined, is immutable.
One can achieve the same effect in a more safe way, by calling another callable unit of the language (template or function) and passing the wanted new value as an argument.
I. Here is a simple example. The following transformation calculates the factorial of the integer, obtained from the string value of the document (top) element of the source XML document:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/*/text()" name="factorial">
<xsl:param name="pN" select="."/>
<xsl:param name="pAccum" select="1"/>
<xsl:value-of select="substring($pAccum, 1 div not($pN > 1))"/>
<xsl:apply-templates select="self::node()[$pN > 1]">
<xsl:with-param name="pN" select="$pN -1"/>
<xsl:with-param name="pAccum" select="$pAccum * $pN"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following source XML document:
<t>9</t>
the wanted, correct result is produced:
362880
In the code above, we see how the value of the parameter $pN is "decreased" from call to call, until it reaches 1, and the value of the parameter $pAccum is multiplied on each call by the value of the parameter $pN.
Do note, however, that we do not modify any parameter at all -- on each call a new instance of the parameter(s) is created, having the same name(s), but living only in the inner scope (call).
II. Often we can avoid the need for recursion: The following XSLT 1.0 transformation calculates and outputs the cubes of the numbers from 1 to 20:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vDoc" select="document('')"/>
<xsl:template match="/*" name="cubes">
<xsl:param name="pN" select="20"/>
<xsl:for-each select=
"($vDoc//node() | $vDoc//node()/#* | $vDoc//namespace::*)
[not(position() > $pN)]">
<xsl:variable name="vM" select="position()"/>
<xsl:value-of select="$vM*$vM*$vM"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any source XML document (not used), the wanted, correct result is produced:
1
8
27
64
125
216
343
512
729
1000
1331
1728
2197
2744
3375
4096
4913
5832
6859
8000
Do note how the standard XPath function position() is called to produce the "loop counter" :). This is the well known method of Piez.
Starting from XSLT versions 2.0 and above, no such tricks are necessary. One can simply write the following, using an XPath 2.0 range expression:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vDoc" select="document('')"/>
<xsl:template match="/*" name="cubes">
<xsl:param name="pN" select="20"/>
<xsl:for-each select="1 to $pN">
<xsl:value-of select=". * . *."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Look into the xsl:number element- you can do something like this:
<xsl:variable name="numberVariable">
<xsl:number/>
</xsl:variable>
<xsl:value-of select="$numberVariable * 2"/>
It's difficult to know exactly what you're doing from your question, but hopefully this can point you in the right direction.

choosing specific column value from the input xml

i have the below xml as input for which i have to do the xsl transformation
<emml>
<tradeEventHeader>
<tradeIdentifier>
<tradeId>104823343913</tradeId>
<systemReference>RDS</systemReference>
<systemDomainName>Internal</systemDomainName>
</tradeIdentifier>
<tradeStateIdentifier>
<tradeStateId>Validated</tradeStateId>
<systemReference>RDS</systemReference>
<tradeStateIdClassificationScheme>Vn State</tradeStateIdClassificationScheme>
</tradeStateIdentifier>
<tradeStateIdentifier>
<tradeStateId>Pending</tradeStateId>
<systemReference>Swapswire</systemReference>
<tradeStateIdClassificationScheme>Mang State</tradeStateIdClassificationScheme>
</tradeStateIdentifier>
<tradeStateIdentifier>
<tradeStateId>accpt_novated_sw</tradeStateId>
<systemReference>RDS</systemReference>
<tradeStateIdClassificationScheme>Clearing State</tradeStateIdClassificationScheme>
</tradeStateIdentifier>
</tradeEventHeader>
<emmlExtension systemId="RDS YTO">
<emmlMediumString idref="legId1" name="Roll Date Option">Short Initial</emmlMediumString>
</emmlExtension>
</emml>
as shown above in the input xml basically my objective is to identify the value of tradeStateIdClassificationScheme parameter and if the value of this parameter is equal to 'Clearing state' then with correspond to that i have to check the value of another column tradeStateId and if the value of the column tradeStateId starts with accpt_novated_sw then in that case we need to return true string and for rest other i need to return false string ..
i have come up with the below template in xslt 1.0 , please advise is it correct approach..
calling template :-
<isClearedNovated>
<xsl:call-template name="cleared_novated">
<xsl:with-param name="tradeStateId" select="emml/*/*/tradeStateIdentifier" />
</xsl:call-template>
</isClearedNovated>
called template :-
<xsl:template name="cleared_novated">
<xsl:param name="tradeStateId" />
<xsl:for-each select="$tradeStateId/tradeStateIdClassificationScheme">
<xsl:choose>
<xsl:when test="$tradeStateId[starts-with(tradeStateIdClassificationScheme,'accpt')] and systemReference='RDS'">
<xsl:value-of select="'true'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'false'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
I don't really understand, what exactly your needs are, but your XSLT probably does not what you want - I suspect it does nothing...
So maybe we can start with the suggestion below and you can tell, what has to be refined:
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="//tradeStateIdClassificationScheme"/>
</xsl:template>
<xsl:template match="tradeStateIdClassificationScheme[
. = 'Clearing State' and
../tradeStateId = 'accpt_novated_sw' and
../systemReference = 'RDS'
]">
<xsl:value-of select="concat(.,': true
')"/>
</xsl:template>
<xsl:template match="tradeStateIdClassificationScheme">
<xsl:value-of select="concat(.,': false
')"/>
</xsl:template>
<xsl:template match="#*|*"/>
</xsl:transform>
You find two templates dealing with tradeStateIdClassificationScheme, one matches your conditions, and one for all others.
Note that you didn't write about the contents of systemReference, while your trial template addresses this element. Therefore, I added this condition as well.
The output in this version is:
Vn State: false
Mang State: false
Clearing State: true

XSLT: nested for-each and dynamic variable

This is my XML and XSLT code
<root>
<act>
<acts id>123</acts>
</act>
<comp>
<comps id>233</comps>
</comp>
</root>
<xsl:for-each select="act/acts">
<xsl:variable name="contactid" select="#id"/>
<xsl:for-each select="root/comp/comps">
<xsl:variable name="var" select="boolean(contactid=#id)"/>
</xsl:for-each>
<xsl:choose>
<xsl:when test="$var='true'">
. . . do this . . .
</xsl:when>
<xsl:otherwise>
. . . do that . . .
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
I want to dynamically assign true or false to var and use it inside <xsl:choose> for boolean test. I hope this helps to find a better solution to get rid of for-each also
First thing to note is that variables in XSLT are immutable, and cannot be changed once initialised. The main problem with your XSLT is that you define your variable within an xsl:for-each block and so it only exists within the scope of that block. It is not a global variable. A new variable gets defined each time that can only be used within the xsl:for-each
From looking at your XSLT it looks like you want to iterate over the acts element and perform a certain action depending on whether an comps element exists with the same value. An alternative approach would be to define a key to look up the comps elements, like so
<xsl:key name="comps" match="comps" use="#id" />
Then you can simply check whether a comps element exists like so (assuming you are positioned on an acts element.
<xsl:choose>
<xsl:when test="key('comps', #id)">Yes</xsl:when>
<xsl:otherwise>No</xsl:otherwise>
</xsl:choose>
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="comps" match="comps" use="#id" />
<xsl:template match="/root">
<xsl:apply-templates select="act/acts" />
</xsl:template>
<xsl:template match="acts">
<xsl:choose>
<xsl:when test="key('comps', #id)"><res>Yes</res></xsl:when>
<xsl:otherwise><res>No</res></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When applied to the following (well-formed) XML
<root>
<act>
<acts id="123"/>
</act>
<comp>
<comps id="233"/>
</comp>
</root>
The following is output
No
However, it can often be preferably in XSLT to avoid the use of conditional statements like xsl:choose and xsl:if. Instead, you can structure the XSLT to make use of template matching. Here is the alternate approach
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="comps" match="comps" use="#id" />
<xsl:template match="/root">
<xsl:apply-templates select="act/acts" />
</xsl:template>
<xsl:template match="acts[key('comps', #id)]">
<res>Yes</res>
</xsl:template>
<xsl:template match="acts">
<res>No</res>
</xsl:template>
</xsl:stylesheet>
When applied to the same XML, the same result is output. Do note the more specific template for the acts node will take priority when matching the case where a comps exist.
There are some errors in your xml file, but assuming what you mean is:
<root>
<act><acts id="123"></acts></act>
<comp><comps id="233"></comps></comp>
</root>
Here is a full solution:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<doc>
<xsl:apply-templates select="root/comp/comps"/>
</doc>
</xsl:template>
<xsl:template match="root/comp/comps">
<xsl:variable name="compsid" select="#id"></xsl:variable>
<xsl:choose>
<xsl:when test="count(/root/act/acts[#id=$compsid])>0">Do This</xsl:when>
<xsl:otherwise>Do That</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

XSLT 1.0 - conditional node assignment

using pure XSLT 1.0, how can I conditionally assign the node. I am trying something like this but it's not working.
<xsl:variable name="topcall" select="//topcall"/>
<xsl:variable name="focusedcall" select="//focusedcall" />
<xsl:variable name="firstcall" select="$topcall | $focusedcall"/>
For variable firstcall, I am doing the conditional node selection. if there is a topcall then assign it to firstcall, othersie assign firstcall to the focusedcall.
This should work:
<xsl:variable name="firstcall" select="$topcall[$topcall] |
$focusedcall[not($topcall)]" />
In other words, select $topcall if $topcall nodeset is non-empty; $focusedcall if $topcall nodeset is empty.
Re-Update regarding "it can be 5-6 nodes":
Given that there may be 5-6 alternatives, i.e. 3-4 more besides $topcall and $focusedcall...
The easiest solution is to use <xsl:choose>:
<xsl:variable name="firstcall">
<xsl:choose>
<xsl:when test="$topcall"> <xsl:copy-of select="$topcall" /></xsl:when>
<xsl:when test="$focusedcall"><xsl:copy-of select="$focusedcall" /></xsl:when>
<xsl:when test="$thiscall"> <xsl:copy-of select="$thiscall" /></xsl:when>
<xsl:otherwise> <xsl:copy-of select="$thatcall" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
However, in XSLT 1.0, this will convert the output of the chosen result to a result tree fragment (RTF: basically, a frozen XML subtree). After that, you won't be able to use any significant XPath expressions on $firstcall to select things from it. If you need to do XPath selections on $firstcall later, e.g. select="$firstcall[1]", you then have a few options...
Put those selections into the <xsl:when> or <xsl:otherwise> so that they happen before the data gets converted to an RTF. Or,
Consider the node-set() extension, which converts an RTF to a nodeset, so you can do normal XPath selections from it. This extension is available in most XSLT processors but not all. Or,
Consider using XSLT 2.0, where RTFs are not an issue at all. In fact, in XPath 2.0 you can put normal if/then/else conditionals inside the XPath expression if you want to.
Implement it in XPath 1.0, using nested predicates like
:
select="$topcall[$topcall] |
($focusedcall[$focusedcall] | $thiscall[not($focusedcall)])[not($topcall)]"
and keep on nesting as deep as necessary. In other words, here I took the XPath expression for 2 alternatives above, and replaced $focusedcall with
($focusedcall[$focusedcall] | $thiscall[not($focusedcall)])
The next iteration, you would replace $thiscall with
($thiscall[$thiscall] | $thatcall[not($thiscall)])
etc.
Of course this becomes hard to read, and error-prone, so I would not choose this option unless the others aren't feasible.
Does <xsl:variable name="firstcall" select="($topcall | $focusedcall)[1]"/> do what you want? That is usually the way to take the first node in document order of different types of nodes.
I. XSLT 1.0 Solution This short (30 lines), simple and parameterized transformation works with any number of node types/names:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pRatedCalls">
<call type="topcall"/>
<call type="focusedcall"/>
<call type="normalcall"/>
</xsl:param>
<xsl:variable name="vRatedCalls" select=
"document('')/*/xsl:param[#name='pRatedCalls']/*"/>
<xsl:variable name="vDoc" select="/"/>
<xsl:variable name="vpresentCallNames">
<xsl:for-each select="$vRatedCalls">
<xsl:value-of select=
"name($vDoc//*[name()=current()/#type][1])"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<xsl:copy-of select=
"//*[name()
=
substring-before(normalize-space($vpresentCallNames),' ')]"/>
</xsl:template>
</xsl:stylesheet>
When applied to this XML document (do note the document order doesn't coincide with the specified priorities in the pRatedCalls parameter):
<t>
<normalcall/>
<focusedcall/>
<topcall/>
</t>
produces exactly the wanted, correct result:
<topcall/>
when the same transformation is applied to the following XML document:
<t>
<normalcall/>
<focusedcall/>
</t>
again the wanted and correct result is produced:
<focusedcall/>
Explanation:
The names of the nodes that are to be searched for (as many as needed and in order of priority) are specified by the global (typically externally specified) parameter named $pRatedCalls.
Within the body of the variable $vpresentCallNames we generate a space-separated list of names of elements that are both specified as a value of the type attribute of a call elementin the$pRatedCalls` parameter and also are names of elements in the XML document.
Finally, we determine the first such name in this space-separated list and select all elements in the document, that have this name.
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pRatedCalls" select=
"'topcall', 'focusedcall', 'normalcall'"/>
<xsl:template match="/">
<xsl:sequence select=
"//*
[name()=$pRatedCalls
[. = current()//*/name()]
[1]
]"/>
</xsl:template>
</xsl:stylesheet>