tags:

views:

83

answers:

4

Hi,

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 number etc.

+1  A: 

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"&gt;
    <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>&#10;</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.

Mads Hansen
A: 

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"&gt;
<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

aSeptik
this one looks for me easy and just works. is there a problem with this one? I think the interesting thing is the <xsl:sort>. what is the advantage of the other solutions?
prometoys
But you yourself defined the problem in such a way that you do not want sorting!`How can I solve this problem with XSLT without sorting `
Dimitre Novatchev
hi,I didnt wan't to sort the content, right, but I don't have problem with xsl:sort itself. I understand this that it is sorted by "line numbers" - with position() - but not the values itself. I tried it with our data and xsl:sort with position works perfectly
prometoys
@prometoys, Then, please, edit your question, because as of now it makes little sense.
Dimitre Novatchev
+3  A: 

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"&gt;
 <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()], '&#xA;')"/>

        <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"&gt;
    <xsl:output method="text"/>

  <xsl:template match="/*">
   <xsl:value-of select="reverse(*)/string(.)"
    separator="&#xA;"/>
  </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the same XML document, the same correct result is produced.

Dimitre Novatchev
I like the second xslt2.0 solution, but I got this error:saxon-xslt test.xml x2.xsl Error at xsl:value-of on line 8 of file:/tmp/x2.xsl: Error in expression reverse(*)/string(.): Unknown system function: reversex2.xsl is the same like your solution
prometoys
@prometoys, This means you are running a Saxon version that is XSLT 1.0 only (Pre-Saxon 7). Download Saxon9 HE from saxonica, or use a non-saxon XSLT 2.0 processor, such as AltovaXML.
Dimitre Novatchev
Dimitre Novatchev
hi, indeed, its saxon 6.5 in Ubuntu. I found a comment, that this should be XSLT 2.0 capable. I will try a real XSLT 2.0 processor
prometoys
A: 

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"&gt;

    <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="'&#x0a;'"/>
      </xsl:for-each>

    </xsl:template>
</xsl:stylesheet>

And the result:

z
c
x
a
Ilian