How to find smallest string in result tree fragment(RTF) containing two strings without using extension function e.g. node-set in xslt 1.0 - xslt

I have two strings variables basically storing currency codes which can have values like USD or EUR or JPY etc.
Examples:
If variable boughtccy contains 'JPY' and variable soldccy contains 'USD' then it should return a string 'BOUGHTCCY' signifying that it is the variable named boughtccy actually contains the smallest currency.
Similarly, if variable boughtccy contains 'EUR' and variable soldccy contains 'AUD' then it should return a string 'SOLDCCY' signifying that it is the variable named soldccy actually contains the smallest currency.
I have written following code which works perfectly fine except for in Altova XMLSpy. I have a requirement that it should also work in XMLSpy.
Here is the code:
xsl:variable name="smallerccy">
<xsl:variable name="nodes">
<node>
<xsl:value-of select="$boughtccy"/>
</node>
<node>
<xsl:value-of select="$soldccy"/>
</node>
</xsl:variable>
<xsl:for-each select="common:node-set($nodes)/*">
<xsl:sort select="."/>
<xsl:choose>
<xsl:when test="position()=1 and .=$boughtccy">BOUGHTCCY</xsl:when>
<xsl:when test="position()=1 and .=$soldccy">SOLDCCY</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
How do I achieve this without using exslt:node-set() function? I cannot use xslt 2.0.

I believe the underlying problem you are having is that in XSLT 1.0 strings can only compared only for equality (or inequality), and so you can simply do <xsl:when test="$boughtccy > $soldccy">
In you particular case, where you are dealing with a finite number of strings, you could define a variable containing all possible currency codes, and use string handling to find one is first.
Try this XSLT (You would, of course, have to amend $AllCurrencies to have all possible currency codes).
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="AllCurrencies" select="'AUS|EUR|JPY|USD'" />
<xsl:param name="boughtccy" select="'JPY'" />
<xsl:param name="soldccy" select="'USD'" />
<xsl:template match="/">
<xsl:choose>
<xsl:when test="string-length(substring-before($AllCurrencies, $boughtccy)) < string-length(substring-before($AllCurrencies, $soldccy))">BOUGHTCCY</xsl:when>
<xsl:otherwise>SOLDCCY</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

I would write conditional logic, using system-property() and function-available(), that implements this in different ways for different processors:
if it's XSLT 2.0, use min(($boughtccy, $soldccy))
if it's XSLT 1.0 with a node-set extension, use the sorting technique that you're already using
otherwise give up. (There is no way to compare two strings for "<" in XSLT 1.0 other than creating a node-set and sorting it, and that requires the node-set extension).

Related

xsl for-each to create an iterable node list

I need to iterate over the elements of an XML file in sorted order of their language field many times. What I try is to get an iterable list of the languages as follows:
<xsl:variable name="languages">
<xsl:for-each select="elem/FIELD[#NAME='language']">
<xsl:sort select="."/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:variable>
While I can verify with a
<xsl:value-of select="$languages"/>
that the sorting works, I cannot iterate like
<xsl:for-each select="$langauges">...</xsl:for-each>
because the XSL processor complains that select expression does not evaluate to a node set.
Edit: Not sure whether this is important, but I have
<xsl:output encoding="UTF-8"
method="xml"
media-type="text/xml"
indent="yes" />
What do I have to insert in the loop to make the result into a node set? Is this at all possible?
Given you say that
XSL processor complains that select expression does not evaluate to a node set.
I assume you're using XSLT 1.0 rather than 2.0. In XSLT 1.0 when you declare a variable with content rather than a select attribute, the resulting variable contains something called a "result tree fragment" rather than a node set. You can apply value-of and copy-of to a RTF to send it to the output but you can't navigate into it using XPath expressions.
Most XSLT processors provide some sort of extension function to convert a RTF into a real node set - msxsl for the Microsoft processor or exslt for most others.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl">
<!-- .... -->
<xsl:variable name="languagesRTF">
<xsl:for-each select="elem/FIELD[#NAME='language']">
<xsl:sort select="."/>
<lang><xsl:value-of select="."/></lang>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="languages" select="exsl:node-set($languagesRTF)/lang" />
In XSLT 2.0 there is no distinction between result tree fragments and node sets - they're both treated as sequences - so you don't need the extension function in that version.
Try the following:
To declare variable:
<xsl:variable name="languages">
<xsl:for-each select="elem/FIELD[#NAME='language']">
<xsl:sort select="."/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
And to loop:
<xsl:for-each select="$languages/*">
You need to convert the result tree fragment in your variable into a node-set, using the EXSLT node-set() function.
XSLT 1.0 allows you to process a node-set in sorted order, but it doesn't allow you to save a sorted sequence in a variable (the data model only has sets, not sequences). The only way you can save sorted data in 1.0 is to construct a new tree containing copies of the original elements in a different order, and then use the node-set() extension to make this tree processable.
This changes in XSLT 2.0, which has a data model based on sequences. In 2.0 you can save a sorted sequence of nodes in a variable without copying the nodes into a new tree.

How do I know how many tags an xsl:variable contains?

In XSLT 1.0, if I have an <xsl:variable> declared like that:
<xsl:variable name="ListeEcheances">
<bla/><bli/>
</xsl:variable>
How do I know if it's empty? Or even better: how do I know how many tags it contains? (I know there are 2 tags here, but my real code is a little bit more complex :))
<xsl:when test="$ListeEcheances=''"> returns true (it doesn't count the tags, only the text) ;
<xsl:when test="count($ListeEcheances/*) > 0"> sadly doesn't compile.
Thank you for your help.
This is indeed incorrect and your compiler is correct in throwing an error. You can only count a node set, you cannot count a result tree fragment. What you need is transform the variable in a node-set by using an extension function.
For Saxon 6.5 this would be exsl:node-set.This works with Saxon 6.5 and any processor that supports the EXSLT node-set function (most do). EDIT: Jirka Kosek wrote down a list of node-set extensions per processor, I'm sure yours is in the list.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0" xmlns:exsl="http://exslt.org/common">
<xsl:variable name="ListeEcheances">
<bla/><bli/>
</xsl:variable>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="count(exsl:node-set($ListeEcheances)/*) > 0">
<xsl:text>Larger then zero!</xsl:text>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="utf-8"?>Larger then zero!
Note: if you were to use XSLT 2.0, everything is a node-set and you don't run into this awkwardness of XSLT 1.0, where result tree fragments are next to useless.
If the content of the variable is declared in the XSLT as shown in your example, rather than dynamically evalueated, you can use the document() function to parse the XSLT file(which is an XML file) and evaluate an XPath expression to count the elements in the variable:
count(document('')/*/xsl:variable[#name='ListeEcheances']/*)
Using the document function with an empty path will load the base URI of the current stylesheet.
Try <xsl:when test="count($ListeEcheances/*) > 0">
or wait - maybe you get something like
Expression must evaluate to a node-set.
count(-->$ListeEcheances<--/*) > 0
The reason is that the variable is a result tree fragment, not a node-set.
In XSLT 1.0 you will need to apply the node-set function, available in a namespace dependent on the processor.
For instance: <xsl:when test="count(msxsl:node-set($ListeEcheances/*)) > 0">
If that does not work, or if you can't discover the namespace to use, then a trick might help:
<xsl:variable name="temp" select="$ListeEcheances"/>
<xsl:when test="count($temp/*) > 0">
The reason that this works can be found in stackoverflow rtf to node-set

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>

Automating exslt:node-set?

Not sure if this is possible, but trying set up something that doesn't make me have to type exslt:node-set when pulling values from a dynamically created node block. I am storing the entire set of nodes in a variable, and wrapping it in exslt:node-set, but why does it not work when I then try to pull from it. Is this possible?
<xsl:variable name="LANG">
<xsl:variable name="tmp">
<xsl:element name="foo">
<xsl:element name="bar">Hello</xsl:element>
</xsl:element>
</xsl:variable>
<xsl:value-of select="exslt:node-set($tmp)"/>
</xsl:variable>
<!-- Love to be able to do this -->
<xsl:value-of select="$LANG/foo/bar"/>
<!-- This does work -->
<xsl:value-of select="exslt:node-set($LANG)/foo/bar"/>
In XSLT 1.0, the variable defined as in your example are called result tree fragments (RTF) and you can only use xsl:copy-of to copy the entire fragment to the result tree or xsl:value-of to copy the entire content. Example
<xsl:copy-of select="$LANG"/>
If you want treat the variable as a temporary tree you need the node-set() extension.
The common way to deal with static tree fragments (like lookup tables) in XSLT 1.0 is to define them as children of the stylesheet root elements (using a custom namespace). Then you can use the document() function to retrieve the wanted value.
Note If you are using Saxon (v>6.5), you could simply set the stylesheet version to 1.1 and you will be able to manage the RTF without any node-set extension.
[XSLT 1.0]
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:empo="http://stackoverflow.com/users/253811/empo">
<empo:LANG>
<empo:foo>
<empo:bar>Hello</empo:bar>
</empo:foo>
</empo:LANG>
<xsl:template match="/">
<xsl:variable name="LANG" select="document('')/*/empo:LANG"/>
<xsl:value-of select="$LANG/empo:foo/empo:bar"/>
</xsl:template>
</xsl:stylesheet>

XSL: How best to store a node in a variable and then use it in future xpath expressions?

I need to be able to store a node set in variable and then perform more filting/sorting on it afterward. All the examples I've seen of this involve either using XSL2 or extensions neither of which are really an option.
I've a list of hotels in my XML doc that can be sorted/filtered and then paged through 5 at a time. I'm finding though I'm repeating alot of the logic as currently I've not found a good way to store node-sets in xsl variable and then use xpath on them for further filtering/sorting.
This is the sort of thing I'm after (excuse the code written of the top of my head so might not be 100%):
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="3_star_or_less" select="/results/hotels[number(rating) <= 3]" />
<xsl:for-each select="3_star_or_less">
<xsl:sort select="rating" />
</xsl:for-each>
Has anyone got an example of how best to do this sort of thing?
Try this example:
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="three_star_or_less"
select="$hotels[number(rating) <= 3]" />
<xsl:for-each select="$three_star_or_less">
<xsl:sort select="rating" />
<xsl:value-of select="rating" />
</xsl:for-each>
There is no problem storing a node-set in a variable in XSLT 1.0, and no extensions are needed. If you just use an XPath expression in select attribute of xsl:variable, you'll end up doing just that.
The problem is only when you want to store the nodes that you yourself had generated in a variable, and even then only if you want to query over them later. The problem here is that nodes you output don't have type "node-set" - instead, they're what is called a "result tree fragment". You can store that to a variable, and you can use that variable to insert the fragment into output (or another variable) later on, but you cannot use XPath to query over it. That's when you need either EXSLT node-set() function (which converts a result tree fragment to a node-set), or XSLT 2.0 (in which there are no result tree fragments, only sequences of nodes, regardless of where they come from).
For your example as given, this doesn't seem to be a problem. Rubens' answer gives the exact syntax.
Another note, if you want to be able to use the variable as part of an XPath statement, you need to select into the variable with <xsl:copy-of select="."/> instead of <xsl:value-of select="."/>
value-of will only take the text of the node and you wont be able to use the node-set function to return anything meaningful.
<xsl:variable name="myStringVar">
<xsl:value-of select="."/>
</xsl:variable>
<!-- This won't work: -->
<Output>
<xsl:value-of select="node-set($myStringVar)/SubNode" />
</Output>
<xsl:variable name="myNodeSetVar">
<xsl:copy-of select="."/>
</xsl:variable>
<!-- This will work: -->
<Output>
<xsl:value-of select="node-set($myNodeSetVar)/SubNode" />
</Output>