What is the differences between these three blocks in terms of side-effects when $world is a list of elements? I am seeing a different behaviour between the first and the third and cannot get my head around it.
<xsl:variable name="hello" select="$world" />
<xsl:variable name="hello">
<xsl:value-of select="$world" />
</xsl:variable>
<xsl:variable name="hello">
<xsl:choose>
<xsl:when test="$something=true()">
<xsl:value-of select="$world" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$world" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Edit 1: I want to process $hello in a <xsl:for-each select="$hello">. With the third block above the <xsl:for-each> has only one item to process that contains the joined contents of $world. Why is that?
The first xsl:variable will have the same value and type as $world. The second is a result tree fragment with a single text node of the string value of $world. The third is also a result tree fragment with a single text node.
I guess you want either
<xsl:variable name="hello" select="if (condition) then $world else $foo"/>
in XSLT 2.0 and then your for-each select="$hello" would work as you want or in XSLT 1.0 plus EXSLT common you want
<xsl:variable name="hello">
<xsl:choose>
<xsl:when test="condition">
<xsl:copy-of select="$world"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$foo"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:for-each select="exsl:node-set($hello)/*">...</xsl:for-each>
While all three examples are valid in both XSLT 1.0 and XSLT 2.0, the way the semantics are described is very different in the two specs; also when $value contains multiple nodes, the effect of <xsl:value-of select="$value"/> depends on whether the stylesheet specifies version="1.0" or version="2.0".
The main things to remember, that apply to both versions, are (a) xsl:value-of creates a text node by converting whatever it selects into a string, and (b) xsl:variable with contained instructions (and no "as" attribute) creates a new tree rooted at a document node.
<xsl:variable name="hello">
<xsl:choose>
<xsl:when test="$something=true()">
<xsl:value-of select="$world" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$world" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
I want to process $hello in a
<xsl:for-each select="$hello">. With
the block above the <xsl:for-each> has
only one item to process that contains
the joined contents of $world. Why is
that?
The variable named $hello contains the string value of $world. This is by definition how <xsl:value-of> behaves in XSLT 1.0.
You haven't shown us how $world is defined, but if it contains a single element or a whole document tree, then (again) by definition, its string value is the concatenation (in document order) of all of its descendents - text nodes.
This is exactly what you are seeing.
The situation will be different if insead of:
<xsl:value-of select="$world" />
you use:
<xsl:copy-of select="$world" />
This copies the whole subtree whose root is the element (or root node / in the case when $world contains a complete document) contained in $world.
However, in XSLT 1.0 this creates the so called RTF (Result Tree Fragment) and by definition one cannot use an RTF as a location step in XPath (1.0) expression.
One must first convert this to a regular tree (document node) using a vendor-supplied extension function that most often has the local-name node-set but is in a vendor-specific namespace.
A typical example is:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vWorld" select="/*"/>
<xsl:template match="/">
<xsl:variable name="vrtfHello">
<xsl:copy-of select="$vWorld"/>
</xsl:variable>
<xsl:variable name="vHello" select=
"ext:node-set($vrtfHello)/*"/>
<xsl:copy-of select="$vHello/*[3]"/>
</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>
the result is (as expected):
<num>03</num>
Here we use the ext:node-set() extension function in the namespace "http://exslt.org/common" as specified by the EXSLT vendor-independent library. Most XSLT 1.0 processors support EXSLT and using its node-set() extension function doesn't decrease the degree of portability of an XSLT application accross all such XSLT processors.
Related
I'm trying to solve a problem, where I have to translate strings using xslt.
I saw this: XSLT key() lookup
and this: XSLT Conditional Lookup Table
but I'm not able to get it to work. I've tried to come up with the minimal example below which shows the problems that I'm facing.
The "real" xsl is assembled from code snippets using a build process. This involves some constraints.
The inner structure of the translation lookup tables always is the same, since they are downloaded from a translation tool in flat xml format http://docs.translatehouse.org/projects/translate-toolkit/en/latest/formats/flatxml.html. I can only wrap them into distinct parent nodes which is what i tried using the "lu" namespace.
The translation tables for all languages have to be stored inside the xsl, because different generations of xsl with different translations may exist next to each other. So no "sidecar" files.
Until now I can't get the key to work. The output of xsltproc is the following:
Setup Key - Start
German
xsltApplyOneTemplate: key was not compiled
Setup Key - End
de # skipped #
de # failed #
Expected output:
Setup Key - Start
German
Setup Key - End
de # skipped # Übersprungen
de # failed # Fehlgeschlagen
The XML file just needs to contain a root element.
So obviously the way I try to define the key depending on the target language is wrong, but my xsl knowledge has reached its limit now. The language stays the same during the transformation, so the key for all translation lookups has to be set up only once at the beginning.
The xsl Transformation:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:lu="http://www.my.domain.de/lookup"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="vLanguageCode">
<!-- <xsl:value-of select="/root/#language"/> -->
<xsl:value-of select="'de'"/>
</xsl:variable>
<xsl:template match="/">
<xsl:call-template name="setupKey"/>
<xsl:call-template name="getLabel">
<xsl:with-param name="pKey" select="'skipped'"/>
</xsl:call-template>
<xsl:call-template name="getLabel">
<xsl:with-param name="pKey" select="'failed'"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="setupKey">
<xsl:message>Setup Key - Start</xsl:message>
<xsl:choose>
<xsl:when test="$vLanguageCode='DE' or $vLanguageCode='de'">
<xsl:message>German</xsl:message>
<xsl:key name="kLanguageDict" match="/lu:de/root/str" use="#key"/>
</xsl:when>
<xsl:otherwise>
<xsl:message>English (default)</xsl:message>
<xsl:key name="kLanguageDict" match="/lu:en/root/str" use="#key"/>
</xsl:otherwise>
</xsl:choose>
<xsl:message>Setup Key - End</xsl:message>
</xsl:template>
<xsl:template name="getLabel">
<xsl:param name="pKey"/>
<xsl:variable name="vResult">
<xsl:value-of select="key('kLanguageDict', $pKey)/#str"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$vResult!=''">
<xsl:value-of select="$vResult"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$pKey"/>
</xsl:otherwise>
</xsl:choose>
<xsl:message>
<xsl:value-of select="concat($vLanguageCode, ' # ', $pKey, ' # ', $vResult)"/>
</xsl:message>
</xsl:template>
<lu:de>
<root>
<str key="skipped">Übersprungen</str>
<str key="failed">Fehlgeschlagen</str>
</root>
</lu:de>
<lu:en>
<root>
<str key="skipped">Skipped</str>
<str key="failed">Failed</str>
</root>
</lu:en>
</xsl:stylesheet>
Additions in response to the answer from #michael.hor257k:
Thank you. I didn't know that. So this means that I can't selectively define a key depending on language?
The translation system originally has one key at the top level and a translation table with interleaved entries for each language. It uses a double index (language+id) to look up the values.
I am trying to find a solution where I can embed the xml files returned by the translation management system (weblate) directly into the xsl without having to modify them. Unfortunately it looks like I'm limited in what I can get back (only default nodes and attributes).
This is the core of the original working translation lookup code:
<xsl:variable name="vLanguageDict" select="document('')/*/lu:strings"/>
<xsl:key name="kLanguageDict" match="lu:string" use="concat(#lang,#id)"/>
<xsl:template name="getLabel">
<xsl:param name="pKey"/>
<xsl:variable name="vResult">
<xsl:for-each select="$vLanguageDict">
<xsl:value-of select="key('kLanguageDict', concat($vLanguageCode,$pKey))/#value" />
</xsl:for-each>
</xsl:variable>
<xsl:choose>
<xsl:when test="$vResult!=''">
<xsl:value-of select="$vResult"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$pKey"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<lu:strings>
<lu:string lang="DE" id="skipped" value="Übersprungen"/>
<lu:string lang="EN" id="skipped" value="skipped"/>
<lu:string lang="DE" id="failed" value="Fehlgeschlagen"/>
<lu:string lang="EN" id="failed" value="failed"/>
</lu:strings>
There are two mistakes in your XSLT stylesheet that immediately jump out:
The xsl:key element is allowed only at the top level, as a child
of the xsl:stylesheet element.
In XSLT 1.0, keys operate only on the current document. If you want to lookup from the stylesheet itself, you must change the context to the stylesheet document before calling the key() function. Here are two examples: https://stackoverflow.com/a/32440143/3016153
https://stackoverflow.com/a/30188334/3016153
I am afraid that's about all that can be said without a reproducible example.
--- added ---
So this means that I can't selectively define a key depending on language?
You cannot define a key conditionally - but you can define more than one key and select the one to use based on the specified language. Here's a simplified example:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dict="http://example.com/dict">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:key name="de" match="dict:de/entry" use="#key" />
<xsl:key name="en" match="dict:en/entry" use="#key" />
<xsl:param name="input">skipped</xsl:param>
<xsl:param name="lang">de</xsl:param>
<xsl:template match="/">
<xsl:value-of select="$lang"/>
<xsl:text> # </xsl:text>
<xsl:value-of select="$input"/>
<xsl:text> = </xsl:text>
<!-- switch context to stylesheet in order to use key -->
<xsl:for-each select="document('')">
<xsl:value-of select="key($lang, $input)"/>
</xsl:for-each>
</xsl:template>
<dict:de>
<entry key="skipped">Übersprungen</entry>
<entry key="failed">Fehlgeschlagen</entry>
</dict:de>
<dict:en>
<entry key="skipped">Skipped</entry>
<entry key="failed">Failed</entry>
</dict:en>
</xsl:stylesheet>
Applied to any XML input, this will return:
Result
de : skipped = Übersprungen
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.
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 :)
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>
I have a constant and a variable that I wann mouch together to select a specific node, this is what I want to do:
<xsl:attribute name="value">
<xsl:value-of>
<xsl:attribute name="select">
<xsl:text>/root/meta/url_params/
<xsl:value-of select="$inputid" />
</xsl:attribute>
</xsl:value-of>
</xsl:attribute>
How come it doesn't work, and what could I do instad?
While #Alejandro is right that in the general case dynamic evaluation will be needed (and this may be provided in XSLT 2.1+), there are manageable simpler cases.
For example, if $inputid contains just a name, you probably want this:
<xsl:value-of select="/root/meta/url_params/*[name()=$inputid]"/>
We can implement a rather general dynamic XPath evaluator if we only restrict each location path to be an element name:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="inputId" select="'param/yyy/value'"/>
<xsl:variable name="vXpathExpression"
select="concat('root/meta/url_params/', $inputId)"/>
<xsl:template match="/">
<xsl:value-of select="$vXpathExpression"/>: <xsl:text/>
<xsl:call-template name="getNodeValue">
<xsl:with-param name="pExpression"
select="$vXpathExpression"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="getNodeValue">
<xsl:param name="pExpression"/>
<xsl:param name="pCurrentNode" select="."/>
<xsl:choose>
<xsl:when test="not(contains($pExpression, '/'))">
<xsl:value-of select="$pCurrentNode/*[name()=$pExpression]"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="getNodeValue">
<xsl:with-param name="pExpression"
select="substring-after($pExpression, '/')"/>
<xsl:with-param name="pCurrentNode" select=
"$pCurrentNode/*[name()=substring-before($pExpression, '/')]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on this XML document:
<root>
<meta>
<url_params>
<param>
<xxx>
<value>5</value>
</xxx>
</param>
<param>
<yyy>
<value>8</value>
</yyy>
</param>
</url_params>
</meta>
</root>
the wanted, correct result is produced:
root/meta/url_params/param/yyy/value: 8
There is no runtime evaluation for XPath expression in standar XSLT 1.0
So, depending what is $inputid, you could have different solutions.
But this /root/meta/url_params/$inputid is wrong because right hand of / must be a relative path in XPath 1.0 (in XPath 2.0 can be a function call, also).
For this particulary case you can use:
/root/meta/url_params/*[name()=$inputid]
or
/root/meta/url_params/*[#id=$inputid]
For a general case, I will go with walker pattern like Dimitre's answer.