Counting distinct items in XSLT and listing only once - xslt

I have the following XML:
<assessment>
<section>
<item>
<attributes>
<variables>
<variable>
<variable_name value="MORTIMER"/>
</variable>
</variables>
</attributes>
</item>
<item>
<attributes>
<variables>
<variable>
<variable_name value="FRED"/>
</variable>
</variables>
</attributes>
</item>
<item>
<attributes>
<variables>
<variable>
<variable_name value="MORTIMER"/>
</variable>
</variables>
</attributes>
</item>
</section>
</assessment>
I have the following XSLT to process that XML:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kValueByVal" match="item//variables//variable_name"
use="#value"/>
<xsl:template match="assessment">
<xsl:for-each select="
.//item//variables//variable_name/#value
">
<xsl:value-of select=
"concat(., ' ', count(key('kValueByVal', .)), '
')"/>
<br/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
It outputs the following, which is almost what I want:
MORTIMER 2
FRED 1
MORTIMER 2
It lists each of the variable_names and how many times each occurs. The only problem is that it gives this count once for each time the variable_name occurs instead of only once.
This is what I want it to output:
MORTIMER 2
FRED 1
How do I modify the XSLT code to give me that? Note that we're using XSLT 1.0.
The following solution, which seems like it should work, outputs nothing:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kValueByVal" match="item//variables//variable_name"
use="#value"/>
<xsl:template match="assessment">
<xsl:for-each select=".//item//variables//variable_name/#value[generate-id()
=
generate-id(key('kValueByVal',.)[1])]">
<xsl:value-of select=
"concat(., ' ', count(key('kValueByVal', .)), '
')"/>
<br/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

You really need to understand how the Muenchian grouping works, otherwise you'd be asking variations of the same questions forever.
Do read Jeni Tennison's tutorial.
Here is a solution for your latest question:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kVarNameByVal" match="variable_name"
use="#value"/>
<xsl:template match=
"variable_name[generate-id()
=
generate-id(key('kVarNameByVal', #value)[1])
]
">
<xsl:value-of select=
"concat(#value, ' ', count(key('kVarNameByVal', #value)), '
')"/>
<br/>
</xsl:template>
</xsl:stylesheet>
When this transformation is performed on the provided XML document, the wanted result is produced:
MORTIMER 2
FRED 1

Seeing as you're using XSLT 2.0, I'd use the built-in grouping features. (For earlier versions, you'd probably want to look into Muenchian grouping.)
<?xml version="1.0" ?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kValueByVal" match="item//variables//variable_name"
use="#value"/>
<xsl:template match="assessment">
<xsl:for-each-group select=".//item//variables//variable_name" group-by="#value">
<xsl:value-of select="concat(current-grouping-key(), ' ', count(current-group()), '
')"/>
<br/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>

This will do it in XSLT 1.0.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kValueByVal" match="item//variables//variable_name"
use="#value"/>
<xsl:template match="assessment">
<xsl:for-each select="
//item//variables//variable_name[not(#value=ancestor::item/preceding-sibling::item//variables//variable_name/#value)]
">
<xsl:value-of select=
"concat(#value, ' ', count(key('kValueByVal', #value)), '
')"/>
<br/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The output I get is
MORTIMER 2
<br />FRED 1
<br />
Note that it assumes a bit more about the document structure (the ancestor::item bit), but you should be able to take it from there.

You get what you are asking for:
<xsl:for-each select=".//item//variables//variable_name/#value">
Wich means: for each one of these attributes
When grouping, you must say: for each one of these one of a kind
And, how do you know wich are one of a kind? With Muenchian method:
<xsl:for-each select=".//item//variables//variable_name/#value[generate-id()
=
generate-id(key('kValueByVal',.)[1])]">
That means: the ones been the first with that key.
EDIT: Also, avoid // when you know input schema.
EDIT: Now I can see that you change the key... So, for you new key, who is first of a kind? Yes! variable_name element:
<xsl:for-each select=".//item//variables//variable_name[generate-id()
=
generate-id(key('kValueByVal',#value)[1])]">

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kValueByVal" match="item//variables//variable_name"
use="#value"/>
<xsl:template match="assessment">
<xsl:for-each
select="//item//variables//variable_name[
generate-id() =
generate-id(key('kValueByVal', #value)[1])]">
<xsl:value-of select=
"concat(./#value, ' ', count(key('kValueByVal', ./#value)), '
')"/>
<br/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Related

How to partition dates with XSLT

I have a group of dates and I'd like to create partitions with a criterion such as "exactly 7 days apart" For example this is my source xml:
<root>
<entry date="2019-05-12" />
<entry date="2019-05-19" />
<entry date="2019-05-26" />
<entry date="2019-06-16" />
<entry date="2019-06-23" />
</root>
The result should be like this:
<root>
<group>
<val>12.5.</val>
<val>19.5.</val>
<val>26.5.</val>
</group>
<group>
<val>16.6.</val>
<val>23.6.</val>
</group>
</root>
since the first three and the last two dates are all on a Sunday without a gap.
What I have so far is this:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:sd="urn:someprefix"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
>
<xsl:output indent="yes"/>
<xsl:template match="root">
<root>
<xsl:copy-of select="sd:partition(distinct-values(for $i in entry/#date return $i cast as xs:date))"/>
</root>
</xsl:template>
<xsl:function name="sd:partition">
<xsl:param name="dates" as="xs:date*"/>
<xsl:for-each-group select="$dates" group-adjacent="format-date(., '[F]')">
<group>
<xsl:for-each select="current-group()">
<val>
<xsl:value-of select="format-date(.,'[D].[M].')"/>
</val>
</xsl:for-each>
</group>
</xsl:for-each-group>
</xsl:function>
</xsl:stylesheet>
Which only generates one group.
How can I ask for the previous element to be 7 days apart? I know of duration (xs:dayTimeDuration('P1D')), but I don't know how to compare it to a previous value.
I use Saxon 9.8 HE.
I think you can also do it using group-adjacent:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="entry/#date/xs:date(.)"
group-adjacent=". - (position() - 1) * xs:dayTimeDuration('P7D')">
<group>
<xsl:apply-templates select="current-group()"/>
</group>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match=".[. instance of xs:date]">
<val>{format-date(.,'[D].[M].')}</val>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/ncdD7mM
To do your grouping, you really need to know the difference in days with the previous element, then you can group starting with dates where the difference is not 7 days. So, you can declare a variable where you build up some new XML with the dates and differences, and then use that to group.
Try this function in your XSLT instead.
<xsl:function name="sd:partition">
<xsl:param name="dates" as="xs:date*"/>
<xsl:variable name="datesWithDiff" as="element()*">
<xsl:for-each select="$dates">
<xsl:variable name="pos" select="position()" />
<date diff="{(. - $dates[$pos - 1]) div xs:dayTimeDuration('P1D')}">
<xsl:value-of select="." />
</date>
</xsl:for-each>
</xsl:variable>
<xsl:for-each-group select="$datesWithDiff" group-starting-with="date[#diff = '' or xs:int(#diff) gt 7]">
<group>
<xsl:for-each select="current-group()">
<val>
<xsl:value-of select="format-date(.,'[D].[M].')"/>
</val>
</xsl:for-each>
</group>
</xsl:for-each-group>
</xsl:function>

Merge and overwrite repeating xml nodes using XSLT 1.0

I have got a source XML
<Records>
<Data>
<RecordType>New</RecordType>
<Number>4734122946</Number>
<DateOfBirth>20160506</DateOfBirth>
<Title>Mr</Title>
<ChangeTimeStamp>20160101010001</ChangeTimeStamp>
<SerialChangeNumber>01</SerialChangeNumber>
</Data>
<Data>
<RecordType>New</RecordType>
<Number>4734122946</Number>
<LastName>Potter</LastName>
<DateOfBirth>20160506</DateOfBirth>
<ChangeTimeStamp>20160101010002</ChangeTimeStamp>
<SerialChangeNumber>01</SerialChangeNumber>
</Data>
</Records>
I want to use XSLT 1.0 to produce the below output
<Contact>
<Number>4734122946</Number>
<Title>Mr</Title>
<LastName>Potter</LastName>
<BirthDate>20160506</BirthDate>
<ChangeTimeStamp>20160101010002</ChangeTimeStamp>
</Contact>
The XSLT has to group and merge the child nodes of Data records into one based on the Number field. Also if there are same elements present, then it should use the ChangeTimeStamp element to figure out the latest change and use that element.
I tried the below XSLT. But I am nowhere close to the output.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes" />
<xsl:key name="groups" match="Data" use="Number"/>
<xsl:key name="sortGroup" match="Data" use ="ChangeTimeStamp"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Records">
<xsl:for-each select="Data[generate-id() = generate-id(key('groups',Number))]">
<Contact>
<Number>
<xsl:value-of select="Number"/>
</Number>
<xsl:for-each select="key('groups',Number)">
<xsl:for-each select="key('sortGroup',ChangeTimeStamp)">
<xsl:sort select="sortGroup" order="ascending"/>
<xsl:if test="Title">
<Title>
<xsl:value-of select ="Title"/>
</Title>
</xsl:if>
<xsl:if test="LastName">
<LastName>
<xsl:value-of select="LastName"/>
</LastName>
</xsl:if>
<BirthDate>
<xsl:value-of select="DateOfBirth"/>
</BirthDate>
</xsl:for-each>
</xsl:for-each>
</Contact>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Appreciate your help.
The XSLT has to group and merge the child nodes of Data records into
one based on the Number field. Also if there are same elements
present, then it should use the ChangeTimeStamp element to figure out
the latest change and use that element.
For that, I believe you would want to do something like:
<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:key name="group" match="Data" use="Number"/>
<xsl:key name="item" match="Data/*" use="concat(../Number, '|', name())"/>
<xsl:template match="/Records">
<root>
<xsl:for-each select="Data[generate-id() = generate-id(key('group', Number)[1])]">
<Contact>
<xsl:for-each select="key('group', Number)/*[generate-id() = generate-id(key('item', concat(../Number, '|', name()))[1])]">
<xsl:for-each select="key('item', concat(../Number, '|', name()))">
<xsl:sort select="../ChangeTimeStamp" data-type="number" order="descending"/>
<xsl:if test="position()=1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</Contact>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
You will have to make some adjustments if you want to include only some data items and/or if you want them to appear in particular order. If you have a list of all possible data item names (e.g. Title, LastName, DateOfBirth, etc.) then this could be simpler.

Filemaker xml output via xslt with column names

I am new to xslt programming and xlm. I have created the code below this works fine, except that instead variable names for each column, it just shows "colno" How do I get the column names into the output?
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fmp="http://www.filemaker.com/fmpxmlresult"
exclude-result-prefixes="fmp"
>
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:variable name="kMetaData" select="fmp:METADATA/fmp:FIELD"/>
<xsl:variable name="colno"
select="count($kMetaData[following-sibling::fmp:FIELD/#NAME]) + 1" />
<xsl:template match="/fmp:FMPXMLRESULT">
<PERSON>
<xsl:apply-templates select="fmp:RESULTSET/fmp:ROW" />
</PERSON>
</xsl:template>
<xsl:template match="fmp:ROW">
<ELEMENTS>
<xsl:apply-templates select="fmp:COL" />
</ELEMENTS>
</xsl:template>
<xsl:template match="fmp:COL">
<xsl:element name="colno">
<xsl:value-of select="fmp:DATA" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
It is hard to make some suggestion without input xml. But at first sight this <xsl:element name="colno"> says "output an element <colno>". I think you should use something like <xsl:element name="{xpath/to/columnName}">
edit:
According to your input xml your template for "COL" element should look like
<xsl:template match="COL">
<xsl:variable name="colPosition" select="position()" />
<!-- Prevent spaces in NAME attribute of FIELD element -->
<xsl:variable name="colName" select="translate($kMetaData[$colPosition]/#NAME, ' ', '_')" />
<xsl:element name="{$colName}">
<xsl:value-of select="DATA"/>
</xsl:element>
</xsl:template>
Then the output looks like
<?xml version="1.0" encoding="utf-8"?>
<PERSON>
<ELEMENTS>
<FIRSTNAME>Richard</FIRSTNAME>
<LASTNAME>Katz</LASTNAME>
<MIDDLENAME>David</MIDDLENAME>
<REQUESTDT>1/1/2001</REQUESTDT>
<salutation>Mr</salutation>
<Bargaining_Unit>CSEA (02,03,04)</Bargaining_Unit>
<Field_134>b</Field_134>
</ELEMENTS>
</PERSON>

Looping Through XSLT

I've got some XML that appears like this
<Data>
<MainItem>
<ItemGroup>Foo</ItemGroup>
<ItemDetails>Details</ItemDetails>
</MainItem>
<MainItem>
<ItemGroup>Bar</ItemGroup>
<ItemDetails>Details</ItemDetails>
</MainItem>
<MainItem>
<ItemGroup>Baz</ItemGroup>
<ItemDetails>Details</ItemDetails>
</MainItem>
<OtherData>
<ItemGroup>Foo</ItemGroup>
<OtherDataDetails>Blah</OtherDataDetails>
</OtherData>
<OtherData>
<ItemGroup>Bar</ItemGroup>
<OtherDataDetails>BlahBlah</OtherDataDetails>
</OtherData>
<OtherData>
<ItemGroup>Baz</ItemGroup>
<OtherDataDetails>BlahBlahBlahBlahBlah</OtherDataDetails>
</OtherData>
</Data>
What I'm trying to transform is something similar to this:
Foo
- Details
- Blah
Bar
- Details
- BlahBlah
Baz
- Details
- BlahBlahBlahBlahBlah
using XSLT 1.0.
I'm currently accomplishing the grouping by doing something similar to the Muenchian method; but I'm not sure how to bring in the data from the tags into my grouping. Any tips?
Try something like this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" />
<xsl:key name="groups" match="//ItemGroup" use="text()" />
<xsl:template match="/">
<Data>
<xsl:apply-templates
select="//ItemGroup[count( . | key('groups', text())[1]) = 1]" />
</Data>
</xsl:template>
<xsl:template match="ItemGroup">
<xsl:variable name="text" select="text()" />
<Group><xsl:value-of select="$text" /></Group>
<xsl:for-each select="/Data/*[ItemGroup = $text]/*[contains(name(), 'Details')]">
<Detail>- <xsl:value-of select="." /></Detail>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I set up a working example for you.
The following grouping solution does not use loops and take care of any other sibling element following the ItemGroup. Moreover only a small key based on MainItem is used to identify the groups.
XSLT 1.0 under Saxon 6.5.5
Producing text:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="main" match="MainItem/ItemGroup" use="text()"/>
<xsl:template match="/Data">
<xsl:apply-templates select="MainItem"/>
</xsl:template>
<xsl:template match="MainItem">
<xsl:value-of select="ItemGroup"/><xsl:text>
</xsl:text>
<xsl:apply-templates select="ItemGroup[generate-id(.)=generate-id(key('main', current()/ItemGroup)[1])]"/>
</xsl:template>
<xsl:template match="ItemGroup">
<xsl:apply-templates select="/Data/*[ItemGroup = current()]/*/following-sibling::*"/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="*">
<xsl:text>- </xsl:text><xsl:value-of select="."/><xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Applied on your input, produces:
Foo
- Details
- Blah
Bar
- Details
- BlahBlah
Baz
- Details
- BlahBlahBlahBlahBlah
Producing XML output:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="main" match="MainItem/ItemGroup" use="text()"/>
<xsl:template match="/Data">
<xsl:copy>
<xsl:apply-templates select="MainItem"/>
</xsl:copy>
</xsl:template>
<xsl:template match="MainItem">
<xsl:variable name="id" select="ItemGroup"/>
<Group id="{$id}">
<xsl:apply-templates select="ItemGroup[generate-id(.)=generate-id(key('main', current()/ItemGroup)[1])]"/>
</Group>
</xsl:template>
<xsl:template match="ItemGroup">
<xsl:copy-of select="/Data/*[ItemGroup = current()]/*/following-sibling::*"/>
</xsl:template>
</xsl:stylesheet>
produces:
<Data>
<Group id="Foo">
<ItemDetails>Details</ItemDetails>
<OtherDataDetails>Blah</OtherDataDetails>
</Group>
<Group id="Bar">
<ItemDetails>Details</ItemDetails>
<OtherDataDetails>BlahBlah</OtherDataDetails>
</Group>
<Group id="Baz">
<ItemDetails>Details</ItemDetails>
<OtherDataDetails>BlahBlahBlahBlahBlah</OtherDataDetails>
</Group>
</Data>
Here is a very short and most efficient transformation that uses only templates and keys:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kGroupByVal" match="ItemGroup"
use="."/>
<xsl:key name="kNonGroupByGroup"
match="*[not(self::ItemGroup)]" use="../ItemGroup"/>
<xsl:template match=
"ItemGroup[generate-id()
=
generate-id(key('kGroupByVal',.)[1])
]
">
<xsl:value-of select="concat('
',.)"/>
<xsl:apply-templates mode="listGroup"
select="key('kNonGroupByGroup',.)"/>
</xsl:template>
<xsl:template match="*" mode="listGroup">
<xsl:value-of select="concat('
- ', .)"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When applied on the provided XML document:
<Data>
<MainItem>
<ItemGroup>Foo</ItemGroup>
<ItemDetails>Details</ItemDetails>
</MainItem>
<MainItem>
<ItemGroup>Bar</ItemGroup>
<ItemDetails>Details</ItemDetails>
</MainItem>
<MainItem>
<ItemGroup>Baz</ItemGroup>
<ItemDetails>Details</ItemDetails>
</MainItem>
<OtherData>
<ItemGroup>Foo</ItemGroup>
<OtherDataDetails>Blah</OtherDataDetails>
</OtherData>
<OtherData>
<ItemGroup>Bar</ItemGroup>
<OtherDataDetails>BlahBlah</OtherDataDetails>
</OtherData>
<OtherData>
<ItemGroup>Baz</ItemGroup>
<OtherDataDetails>BlahBlahBlahBlahBlah</OtherDataDetails>
</OtherData>
</Data>
the wanted, correct result is produced:
Foo
- Details
- Blah
Bar
- Details
- BlahBlah
Baz
- Details
- BlahBlahBlahBlahBlah
** Explanation**:
Muenchian grouping to get the distinct values of ItemGroup.
Key used to index all non-ItemGroup elements by their ItemGroup sibling.
Empty template matching any text node to prevent the built-in XSLT template to output any text node.

XSLT: How to reverse output without sorting by content

I have a list of items:
<item>a</item>
<item>x</item>
<item>c</item>
<item>z</item>
and I want as output
z
c
x
a
I have no order information in the file and I just want to reverse the lines. The last line in the source file should be first line in the output. How can I solve this problem with XSLT without sorting by the content of the items, which would give the wrong result?
I will present two XSLT solutions:
I. XSLT 1.0 with recursion Note that this solution works for any node-set, not only in the case when the nodes are siblings:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:call-template name="reverse">
<xsl:with-param name="pList" select="*"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="reverse">
<xsl:param name="pList"/>
<xsl:if test="$pList">
<xsl:value-of
select="concat($pList[last()], '
')"/>
<xsl:call-template name="reverse">
<xsl:with-param name="pList"
select="$pList[not(position() = last())]"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<t>
<item>a</item>
<item>x</item>
<item>c</item>
<item>z</item>
</t>
produces the wanted result:
z
c
x
a
II. XSLT 2.0 solution :
<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:value-of select="reverse(*)/string(.)"
separator="
"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document, the same correct result is produced.
XML CODE:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<device>
<element>a</element>
<element>x</element>
<element>c</element>
<element>z</element>
</device>
XSLT CODE:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="//device">
<xsl:for-each select="element">
<xsl:sort select="position()" data-type="number" order="descending"/>
<xsl:text> </xsl:text>
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
note: if you're using data-type="number", and any of the values aren't numbers, those non-numeric values will sort before the numeric values. That means if you're using order="ascending", the non-numeric values appear first; if you use order="descending", the non-numeric values appear last.
Notice that the non-numeric values were not sorted; they simply appear in the output document in the order in which they were encountered.
also, you may find usefull to read this:
http://docstore.mik.ua/orelly/xml/xslt/ch06_01.htm
Not sure what the full XML looks like, so I wrapped in a <doc> element to make it well formed:
<doc>
<item>a</item>
<item>x</item>
<item>c</item>
<item>z</item>
</doc>
Running that example XML against this stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:call-template name="reverse">
<xsl:with-param name="item" select="doc/item[position()=last()]" />
</xsl:call-template>
</xsl:template>
<xsl:template name="reverse">
<xsl:param name="item" />
<xsl:value-of select="$item" />
<!--Adds a line feed-->
<xsl:text>
</xsl:text>
<!--Move on to the next item, if we aren't at the first-->
<xsl:if test="$item/preceding-sibling::item">
<xsl:call-template name="reverse">
<xsl:with-param name="item" select="$item/preceding-sibling::item[1]" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Produces the requested output:
z
c
x
a
You may need to adjust the xpath to match your actual XML.
Consider this XML input:
<?xml version="1.0" encoding="utf-8" ?>
<items>
<item>a</item>
<item>x</item>
<item>c</item>
<item>z</item>
</items>
The XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/items[1]">
<xsl:variable name="items-list" select="." />
<xsl:variable name="items-count" select="count($items-list/*)" />
<xsl:for-each select="item">
<xsl:variable name="index" select="$items-count+1 - position()"/>
<xsl:value-of select="$items-list/item[$index]"/>
<xsl:value-of select="'
'"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
And the result:
z
c
x
a