XSLT for-each with complex condition - xslt

I am transforming following XML to generate HTML.
XML
<clause code="section6">
<variable col="1" name="R1C1" row="1">Water</variable>
<variable col="2" name="R1C2" row="1">true</variable>
<variable col="1" name="R2C1" row="2">Gas</variable>
<variable col="2" name="R2C2" row="2"></variable>
<variable col="1" name="R3C1" row="3">Petrol</variable>
<variable col="2" name="R3C2" row="3">true</variable>
<clause>
XSLT
1: <xsl:for-each select="$clause/variable[#col='1']">
2: <xsl:sort select="#row" data-type="number"/>
3: <xsl:variable name="row-id" select="#row"/>
4: <xsl:variable name="row" select="$clause/variable[#row=$row-id]"/>
5: <xsl:if test="$clause/variable[#col='2' and #row=$row-id]='true'">
6: <xsl:value-of name="row-no" select="concat(position(), ') ')"/>
7: <xsl:value-of select="$clause/variable[#col='1' and #row=$row-id]"/>
8: </xsl:if>
9: </xsl:for-each>
The transformation works fine and shows result 1) Water 3) Petrol
The issue is sequence number. You can see condition on Line 5 filters rows that only have 'true' value in col 2 and position() used for displaying sequence number. I cannot have running counter in XLST.
I was wondering if I can add condition of Line 5 with for-each at Line 1. The result with above example should be 1) Water 2) Patrol any advice?

Does this do what you want?
I drive the for-each selection on col 2 being true. That way position, which equals where we are in the selected set of nodes will equal 2 not 3
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:for-each select="clause/variable[#col='2' and text()='true']">
<xsl:sort select="#row" data-type="number"/>
<xsl:variable name="row-id" select="#row"/>
<xsl:value-of select="concat(position(), ') ')"/>
<xsl:value-of select="/clause/variable[#col='1' and #row=$row-id]"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Though I'd probably use templates in preference to the for-each and use current() so that we don't need the row-id variable:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:apply-templates select="clause/variable[#col='2' and text()='true']">
<xsl:sort select="#row" data-type="number"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="clause/variable[#col='2' and text()='true']">
<xsl:value-of select="concat(position(), ') ')"/>
<xsl:value-of select="/clause/variable[#col='1' and #row=current()/#row]"/>
</xsl:template>
</xsl:stylesheet>

I don't think you can express this with a single XPath 1.0 expression.
In XSLT 1.0 I will use keys and the solution becomes short, elegant and efficient:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kVarCol2" match="variable[#col=2]" use="#row"/>
<xsl:template match="/*">
<xsl:for-each select="variable[#col='1'][key('kVarCol2', #row)='true']">
<xsl:sort select="#row" data-type="number"/>
<xsl:value-of select="concat('
', position(), ') ', .)"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document (corrected to be made well-formed):
<clause code="section6">
<variable col="1" name="R1C1" row="1">Water</variable>
<variable col="2" name="R1C2" row="1">true</variable>
<variable col="1" name="R2C1" row="2">Gas</variable>
<variable col="2" name="R2C2" row="2"></variable>
<variable col="1" name="R3C1" row="3">Petrol</variable>
<variable col="2" name="R3C2" row="3">true</variable>
</clause>
the wanted, correct result is produced:
1) Water
2) Petrol
II. XPath 2.0 (single expression) solution:
for $i in 1 to max(/*/*/#row/xs:integer(.))
return
/*/variable[#row eq string($i)]
[#col eq '1'
and
../variable
[#row eq string($i)
and
#col eq '2'
and
. eq 'true'
]
]
/concat('
', position(), ') ', .)
When this XPath 2.0 expression is evaluated on the same XML document (above), the result is the same wanted string:
1) Water
2) Petrol

Related

Getting value in between keys using XSLT

I Need to get the value in the XML file in between each key. For example, I have a list of keys to be used, and each key there's a corresponding output element. The keys can be placed anywhere, there is no proper order in where the key is needed to place. I need to do this in XSLT 2.0, and I don't have any idea on how will I do this.
Keys: Element:
/OPDH/ - ROOT/ELEMENT1/ABCD
/EKPH/ - ROOT/ELEMENT2/POIU
/SGDE/ - ROOT/ELEMENT3/WXYZ
...some other keys...
NOTE: Keys: is in BOLD, and Element is in ITALIC BOLD.
If I have a sample input like this:
1.)
<DATA>/OPDH/FLOWING SOLUTION/SGDE/Number0983713/EKPH/Sample test/some other keys/</DATA>
OR it can be:
2.)
<DATA>/some other keys/afdsf/SGDE/Number0983713/some other keys/PIHSAGA/OPDH/FLOWING SOLUTION/some other keys/No exception/EKPH/Sample test/some other keys/</DATA>
The expected output should look like this:
1.
<ROOT>
<ELEMENT1>
<ABCD>FLOWING SOLUTION</ABCD>
</ELEMENT1>
<ELEMENT2>
<POIU>Sample test</POIU>
</ELEMENT2>
<ELEMENT3>
<SGDE>Number0983713</SGDE>
</ELEMENT3>
...some other keys...
</ROOT>
2.
<ROOT>
...some other keys...
<ELEMENT3>
<SGDE>Number0983713</SGDE>
</ELEMENT3>
...some other keys...
<ELEMENT1>
<ABCD>FLOWING SOLUTION</ABCD>
</ELEMENT1>
...some other keys...
<ELEMENT2>
<POIU>Sample test</POIU>
</ELEMENT2>
...some other keys...
</ROOT>
Thank you.
Here is a partial suggestion that uses analyze-string:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf" version="2.0">
<xsl:param name="keys">
<element key="/OPDH/">ROOT/ELEMENT1/ABCD</element>
<element key="/EKPH/">ROOT/ELEMENT2/POIU</element>
<element key="/SGDE/">ROOT/ELEMENT3/WXYZ</element>
<element key="/some other keys/">ROOT/FOO/BAR</element>
</xsl:param>
<xsl:output indent="yes"/>
<xsl:variable name="pattern" as="xs:string"
select="concat('(', string-join($keys/element/#key, '|'), ')', '(.*?)', '(', string-join($keys/element/#key, '|'), ')')"/>
<xsl:key name="ref" match="element" use="#key"/>
<xsl:function name="mf:extract" as="element()*">
<xsl:param name="input" as="xs:string"/>
<xsl:analyze-string select="$input" regex="{$pattern}">
<xsl:matching-substring>
<xsl:if test="position() eq 1">
<element path="{key('ref', regex-group(1), $keys)}">
<xsl:value-of select="regex-group(2)"/>
</element>
<xsl:sequence
select="mf:extract(substring($input, string-length(concat(regex-group(1), regex-group(2))) + 1))"
/>
</xsl:if>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:function>
<xsl:template match="DATA">
<xsl:copy>
<xsl:sequence select="mf:extract(.)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This transforms the input
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<DATA>/OPDH/FLOWING SOLUTION/SGDE/Number0983713/EKPH/Sample test/some other keys/</DATA>
<DATA>/some other keys/afdsf/SGDE/Number0983713/some other keys/PIHSAGA/OPDH/FLOWING SOLUTION/some other keys/No exception/EKPH/Sample test/some other keys/</DATA>
</Root>
into list of elements with the extracted data and the path to build:
<DATA>
<element path="ROOT/ELEMENT1/ABCD">FLOWING SOLUTION</element>
<element path="ROOT/ELEMENT3/WXYZ">Number0983713</element>
<element path="ROOT/ELEMENT2/POIU">Sample test</element>
</DATA>
<DATA>
<element path="ROOT/FOO/BAR">afdsf</element>
<element path="ROOT/ELEMENT3/WXYZ">Number0983713</element>
<element path="ROOT/FOO/BAR">PIHSAGA</element>
<element path="ROOT/ELEMENT1/ABCD">FLOWING SOLUTION</element>
<element path="ROOT/FOO/BAR">No exception</element>
<element path="ROOT/ELEMENT2/POIU">Sample test</element>
</DATA>
I am not quite sure whether that is doing the right job as I am not sure what determines the order and contents of the two samples you have provided and what e.g. /some other keys/ is meant to express. Tell us whether the result has the data you want or clarify your question and the samples you have shown. It should be easy to generate the XML from the above intermediary results once we have established that the right data is extracted.
You wrote very little about keys, so I assume that:
Your input file contains both:
key list (in KEYS tag),
actual source (in DATA tag).
Both these tags are children of the source ROOT tag.
KEYS tag contains in each row a pair of key value and output path, where
respective content for this key shoud be placed.
Assume that your full input is:
<?xml version="1.0" encoding="UTF-8"?>
<ROOT>
<KEYS>
/OPDH/ - ROOT/ELEMENT1/ABCD
/EKPH/ - ROOT/ELEMENT2/POIU
/SGDE/ - ROOT/ELEMENT3/SGDE
</KEYS>
<DATA>/OPDH/FLOWING SOLUTION/SGDE/Number0983713/EKPH/Sample test/</DATA>
</ROOT>
Then you can write the XSLT as follows:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="ROOT">
<xsl:copy>
<!-- Divide KEYS into rows -->
<xsl:variable name="keys_1" select="tokenize(KEYS, '
')"/>
<!-- # of rows -->
<xsl:variable name="nn" select="count($keys_1)"/>
<!-- Drop 1st and last (empty) row -->
<xsl:variable name="keys" select="subsequence($keys_1, 2, $nn - 2)"/>
<!-- Divide DATA into tokens -->
<xsl:variable name="data_1" select="tokenize(DATA, '/')"/>
<!-- # of tokens -->
<xsl:variable name="nn" select="count($data_1)"/>
<!-- Drop 1st and last (empty) token -->
<xsl:variable name="data" select="subsequence($data_1, 2, $nn - 2)"/>
<!-- Generate output data for each row from keys -->
<xsl:for-each select="$keys">
<!-- Divide the keys row into tokens -->
<xsl:variable name="parts" select="tokenize(., '/')"/>
<!-- # of tokens -->
<xsl:variable name="nn" select="count($parts)"/>
<!-- Source key - token No 2 (after the 1st '/') -->
<xsl:variable name="srcKey" select="$parts[2]"/>
<!-- path - tokens after 'ROOT' -->
<xsl:variable name="path" select="subsequence($parts, 4)"/>
<!-- Open tags given in path -->
<xsl:for-each select="$path">
<xsl:text>
</xsl:text>
<!-- Spacing -->
<xsl:variable name="nn" select="position()"/>
<xsl:value-of select=
"string-join((for $i in 1 to $nn return ' '), '')"/>
<!-- Print opening tag -->
<xsl:value-of select="concat('<', ., '>')"
disable-output-escaping="yes"/>
</xsl:for-each>
<!-- Find position of the source key in data -->
<xsl:variable name="ind" select="index-of($data, $srcKey)[1]"/>
<!-- Get data from the next token -->
<xsl:value-of select="$data[$ind + 1]"/>
<!-- Close tags given in path -->
<xsl:for-each select="reverse($path)">
<xsl:variable name="nn" select="position()"/>
<!-- Spacing and NewLine - but not for the most inner tag -->
<xsl:if test="$nn > 1">
<xsl:text>
</xsl:text>
<xsl:value-of select=
"string-join((for $i in 1 to last() - $nn + 1 return ' '), '')"/>
</xsl:if>
<!-- Print closing tag -->
<xsl:value-of select="concat('</', ., '>')"
disable-output-escaping="yes"/>
</xsl:for-each>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>

How to get maximum or minimum from multiple sets of calculated values with xpath

I need the maximum of three three kinds of values.
I've got a structure similar to this.
note:the first two answers are based on a previous example xml (in set 2 of night the max-above-average was 8). This was confusing, so I changed it to 7.
<data>
<record>
<max>60</max>
</record>
<day>
<set>
<average>49</average>
<max-above-average>3</max-above-average>
</set>
<set>
<average>45</average>
<max-above-average>9</max-above-average>
</set>
</day>
<night>
<set>
<average>50</average>
<max-above-average>5</max-above-average>
</set>
<set>
<average>52</average>
<max-above-average>7</max-above-average>
</set>
</night>
</data>
Now I need the maximum of the record, day and night. This would be maximum: 60, the value of the record in this example: 60 = 60, > 49 + 3, 45 +9, 50 + 5, 52+7. Day and night maximums need to be calculated. Because of this
max(//record/max | //day/set/(average + max-above-average)) | //night/set/(average +max-above-average))
does not work. The |-sign only works for nodes.
It gives following error:
Required item type of second operand of '|' is node(); supplied value has item type xs:double
I'm using xpath 2.0 and xslt 2.0.
Here are the wanted two XPath 2.0 expressions (for producing the "max" and the "min" value, respectively):
max(/*/(day|night)/*/(average+max-above-average))
and
min(/*/(day|night)/*/(average -min-above-average))
XSLT 2.0 - based verification:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
max: <xsl:text/>
<xsl:sequence select=
"max(/*/(day|night)/*/(average+max-above-average))"/>
min: <xsl:text/>
<xsl:sequence select=
"min(/*/(day|night)/*/(average -min-above-average))"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<data>
<record>
<min>40</min>
<max>60</max>
</record>
<day>
<set>
<average>49</average>
<max-above-average>3</max-above-average>
<min-above-average>15</min-above-average>
</set>
<set>
<average>45</average>
<max-above-average>9</max-above-average>
<min-above-average>2</min-above-average>
</set>
</day>
<night>
<set>
<average>50</average>
<max-above-average>5</max-above-average>
<min-above-average>6</min-above-average>
</set>
<set>
<average>52</average>
<max-above-average>8</max-above-average>
<min-above-average>11</min-above-average>
</set>
</night>
</data>
the two XPath expressions are evaluated and the results of these evaluations are copied to the output:
max: 60
min: 34
Update:
The OP says in a comment that he wants "maximum of day and night ànd record" -- I really don't understand what he means by that.
Here is my attempt at guessing:
max(
(/*/record/max,
/*/(day|night)/*/(average+max-above-average, average+min-above-average)
)
)
When implanted in the XSLT transformation (above), this produces:
max: 64
An XSLT 1.0 approach:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/*">
<xsl:variable name="recordMax" select="record/max" />
<xsl:variable name="dayMax">
<xsl:apply-templates select="day" mode="max" />
</xsl:variable>
<xsl:variable name="nightMax">
<xsl:apply-templates select="night" mode="max" />
</xsl:variable>
<xsl:call-template name="Max">
<xsl:with-param name="v1" select="$recordMax" />
<xsl:with-param name="v2">
<xsl:call-template name="Max">
<xsl:with-param name="v1" select="$dayMax" />
<xsl:with-param name="v2" select="$nightMax" />
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="*[set]" mode="max">
<xsl:apply-templates select="set" mode="max">
<xsl:sort select="average + max-above-average"
data-type="number"
order="descending" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="set" mode="max">
<xsl:if test="position() = 1">
<xsl:value-of select="average + max-above-average" />
</xsl:if>
</xsl:template>
<xsl:template name="Max">
<xsl:param name="v1" />
<xsl:param name="v2" />
<xsl:choose>
<xsl:when test="not($v2 > $v1)">
<xsl:value-of select="$v1" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$v2" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, the result is:
60

How to declare a sequence in XSLT?

I need to declare a fixed sequence of numbers. How do I do this?
For example, is it (I'm guessing here):
<xsl:element name="xsl:param">
<xsl:attribute name="name">MySequence</xsl:attribute>
<xsl:sequence>(1,2,3,4)</xsl:sequence>
</xsl:element>
or
<xsl:element name="xsl:param">
<xsl:attribute name="name">MySequence</xsl:attribute>
<xsl:sequence>1,2,3,4</xsl:sequence>
</xsl:element>
or what?
Thanks
If you're using XSLT 2.0, you can just create the sequence directly in the select like:
<xsl:param name="MySequence" select="('1','2','3','4')"/>
XSLT based verification...
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:param name="seq" select="('23453','74365','98','653')"/>
<xsl:template match="/">
<xsl:for-each select="$seq">
<xsl:value-of select="concat('Item ',position(),': ',.,'
')"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
applied to any XML input produces:
Item 1: 23453
Item 2: 74365
Item 3: 98
Item 4: 653
To build a sequence in the XSLT 2.0 sense you use a select e.g.
<xsl:sequence select="1 to 4" />
But if you're adding the value to an element you may prefer value-of
<xsl:value-of select="1 to 4" separator="," />
Given the snippet in the question, this would generate output XML of
<xsl:param name="MySequence">1,2,3,4</xsl:param>
Which makes the value of the generated param a comma separated string. If you actually want the param value to be a sequence in the generated XSLT then you need to generate a select attribute instead of using element content
<xsl:element name="xsl:param">
<xsl:attribute name="name" select="'MySequence'"/>
<xsl:attribute name="select">
<xsl:text>(</xsl:text>
<xsl:value-of select="1 to 4" separator=","/>
<xsl:text>)</xsl:text>
</xsl:attribute>
</xsl:element>
Giving output of
<xsl:param name="MySequence" select="(1,2,3,4)" />

How do I find the number of elements in a <xs:list> using XSLT?

I have an XML schema that contains the following type :
<xs:simpleType name="valuelist">
<xs:list itemType="xs:double"/>
</xs:simpleType>
A sample XML fragment would be:
<values>1 2 3.2 5.6</values>
In an XSLT transform, how do I get the number of elements in the list?
How do I iterate over the elements?
I. XPath 2.0 (XSLT 2.0) solution:
count(tokenize(., ' '))
II. XPath 1.0 (XSLT 1.0) solution:
string-length()
-
string-length(translate(normalize-space(), ' ', ''))
+ 1
As for iteration over the items of this list:
In XPath 2.0 / XSLT 2.0 just use the above XPath 2.0 expression as the value of a select attribute:
--
for $i in tokenize(., ' '),
$n in number($i)
return
yourXPathExpression
--
2. In XSLT 1.0 you need to have some more code for splitting/tokenization. There are several good answers to this question (part of them mine) -- just search for something like "xslt split a string"
If list is strictly spaced, you can count spaces based on string length.
<values>1 2 3.2 5.6</values>
XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<xsl:value-of select="string-length(values)
- string-length(translate(values, ' ', '')) + 1"/>
</xsl:template>
</xsl:stylesheet>
To iterate over elements you have to split this string. There a lot of examples at SO.
In a schema-aware transformation, use count(data(value)).
With a little imagination you can write your own split function :
<xsl:stylesheet version = '1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:output method='text'/>
<xsl:template name="split-list">
<xsl:param name="list" />
<xsl:param name="separator"/>
<xsl:variable name="newlist" select="concat(normalize-space($list), $separator)" />
<xsl:variable name="first" select="substring-before($newlist, $separator)" />
<xsl:variable name="remaining" select="substring-after($newlist, $separator)" />
<xsl:message terminate="no">
<xsl:value-of select="$first" />
</xsl:message>
<xsl:if test="$remaining">
<xsl:call-template name="split-list">
<xsl:with-param name="list" select="$remaining" />
<xsl:with-param name="separator" select="$separator"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="myList">
1 2 3.2 5.6
</xsl:variable>
<xsl:call-template name="split-list">
<xsl:with-param name="list" select="$myList" />
<xsl:with-param name="separator">
<xsl:text> </xsl:text>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
Output :
[xslt] Loading stylesheet D:\Tools\StackOverFlow\test.xslt
[xslt] 1
[xslt] 2
[xslt] 3.2
[xslt] 5.6

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