Finding max of three numbers in XSL - xslt

How can find max of three numbers in XSL ?
More Information : I have three numbers say 1.0, 2.0, 5.0....I don't have any nodes set... I would like to find maximum of 1.0, 2.0 and 5.0.....
Example :
AIM : TO know which type of node <MYNODE>, <DOC>, <PIC>
is having maximum count and whant's the count number ?
<ROOT>
<MYNODES>
<MYNODE>A</MYNODE>
<MYNODE>B</MYNODE>
<MYNODE>C</MYNODE>
<MYNODE>D</MYNODE>
</MYNODES>
<DOCS>
<DOC>1</DOC>
<DOC>2</DOC>
<DOC>3</DOC>
</DOC>
<PICS>
<PIC>a.jpeg</PIC>
<PIC>b.jpeg</PIC>
<PIC>c.jpeg</PIC>
<PIC>d.jpeg</PIC>
<PIC>e.jpeg</PIC>
</PICS>
</ROOT>

With your input XML, you would find the maximum count you are looking for like this:
<xsl:variable name="vMaxChildren">
<xsl:for-each select="/ROOT/*">
<xsl:sort select="count(*)" data-type="number" order="descending" />
<xsl:if test="position() = 1">
<xsl:value-of select="concat(name(), ': ', count(*))" />
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="$vMaxChildren" />
Which would produce:
PICS: 5

This question is incorrectly formulated and the provided "XML document' is not well-formed!
Do note that it is generally meaningless to ask about the maximum of a set of numbers. There can be more than one number with the highest value. Therefore, the solutions below show just the first item with the maximum value.
This is one possible XSLT 1.0 solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="vNameMaxCount">
<xsl:for-each select="*/*">
<xsl:sort select="count(*)" data-type="number"
order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="concat(name(),'+', count(*))"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
One element with maximum children is: <xsl:text/>
<xsl:value-of select="substring-before($vNameMaxCount, '+')"/>
Maximum number of children: <xsl:text/>
<xsl:value-of select="substring-after($vNameMaxCount, '+')"/>
</xsl:template>
</xsl:stylesheet>
when the above transformation is applied on the following XML document (produced from the one provided after spending 10 minutes to make it well-formed!):
<ROOT>
<MYNODES>
<MYNODE>A</MYNODE>
<MYNODE>B</MYNODE>
<MYNODE>C</MYNODE>
<MYNODE>D</MYNODE>
</MYNODES>
<DOCS>
<DOC>1</DOC>
<DOC>2</DOC>
<DOC>3</DOC>
</DOCS>
<PICS>
<PIC>a.jpeg</PIC>
<PIC>b.jpeg</PIC>
<PIC>c.jpeg</PIC>
<PIC>d.jpeg</PIC>
<PIC>e.jpeg</PIC>
</PICS>
</ROOT>
the wanted result is produced
One element with maximum children is: PICS
Maximum number of children: 5
An XSLT 2.0 solution (actually just an XPath 2.0 soulution):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select=
"for $vmaxChildrein in max(/*/*/count(*)),
$vmaxNode in */*[count(*) = $vmaxChildrein][1]
return
(name($vmaxNode),
'has the max no of children:',
$vmaxChildrein
)
"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the above document, the wanted result is produced:
PICS has the max no of children: 5
For finding the maximum of more tricky properties that cannot be immediately expressed as an XPath expression and used in <xsl:sort>, do use the f:maximum() function of FXSL.

5.0 is the largest of the three numbers. Hardcode it and be done with it. :-)
Seriously though, you may wish to take another path. Logic like this is trivial in other languages, but can be a pain in XSLT. You should consider writing a simple extension for the XSLT engine rather than messing around trying to make XSLT do what you want it to.

Related

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

check for successively numbered attributes

I have a situation where I need to check for attribute values that may be successively numbered and input a dash between the start and end values.
<root>
<ref id="value00008 value00009 value00010 value00011 value00020"/>
</root>
The ideal output would be...
8-11, 20
I can tokenize the attribute into separate values, but I'm unsure how to check if the number at the end of "valueXXXXX" is successive to the previous value.
I'm using XSLT 2.0
You can use xsl:for-each-group with #group-adjacent testing for the number() value subtracting the position().
This trick was apparently invented by David Carlisle, according to Michael Kay.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vals"
select="tokenize(root/ref/#id, '\s?value0*')[normalize-space()]"/>
<xsl:variable name="condensed-values" as="item()*">
<xsl:for-each-group select="$vals"
group-adjacent="number(.) - position()">
<xsl:choose>
<xsl:when test="count(current-group()) > 1">
<!--a sequence of successive numbers,
grab the first and last one and join with '-' -->
<xsl:sequence select="
string-join(current-group()[position()=1
or position()=last()]
,'-')"/>
</xsl:when>
<xsl:otherwise>
<!--single value group-->
<xsl:sequence select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:variable>
<xsl:value-of select="string-join($condensed-values, ',')"/>
</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>

xsl get array of elements

Hi
I need get array of elements (before "-" if exist) by xsl.
xml is
<Cars>
<Car Trunck="511"/>
<Car Trunck="483-20"/>
<Car Trunck="745"/>
</Cars>
xsl is
<xsl:variable name="testarr">
<xsl:for-each select="//Cars//Car/#Trunck">
<xsl:value-of select="number(substring(.,1,3))" />
</xsl:for-each>
</xsl:variable>
(i suppose that all numbers is three-digit number, if someone knows a solution for all conditions will be glad to hear the proposal)
if i do this
i get all numbers in one line: 511483745
and i need get them in array
because i also need get the max value
thanks
Hi I need get array of elements
(before "-" if exist) [...] i need get
them in array because i also need get
the max value
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="/Cars/Car/#Trunck">
<xsl:sort select="concat(substring-before(.,'-'),
substring(., 1 div not(contains(.,'-'))))"
data-type="number" order="descending"/>
<xsl:if test="position()=1">
<xsl:value-of
select="concat(substring-before(.,'-'),
substring(.,1 div not(contains(.,'-'))))"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
745
XPath 2.0 one line:
max(/Cars/Car/#Trunck/number(replace(.,'-.*','')))
You could use the substring-before and substring-after functions: See the excellent ZVON tutorial
http://zvon.org/xxl/XSLTreference/Output/function_substring-after.html
In your example you are only extracting the values (which are strings) which get concatenated. Perhaps you need to wrap the result in your own element
<xsl:for-each select="//Cars//Car/#Trunck">
<truck>
<xsl:value-of select="number(substring(.,1,3))" />
</truck>
</xsl:for-each>
While you have two good answers (especially that by #Alejandro), here's one from me that I think is even better:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="pTopNums" select="2"/>
<xsl:template match="/*">
<xsl:apply-templates select="*">
<xsl:sort data-type="number" order="descending"
select="substring-before(concat(#Trunck,'-'),'-')"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Car">
<xsl:if test="not(position() > $pTopNums)">
<xsl:value-of select=
"substring-before(concat(#Trunck,'-'),'-')"/>
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document (the originally provided one, slightly changed to be more challenging):
<Cars>
<Car Trunck="483-20"/>
<Car Trunck="311"/>
<Car Trunck="745"/>
</Cars>
produces the wanted, correct result (the top two numbers that are derived from #Trunck as specified in the question):
745
483