Search code examples
xsltxerces

XSLT: Sort by multiple items


I have XML data such as:

<feed>
  <entry>
    <id>4</id>
    <updated>2011-01-18T16:55:54Z</updated>
    <title>title2</title>
  </entry>
  <entry>
    <id>3</id>
    <updated>2011-01-18T16:55:54Z</updated>
    <title>title1</title>
  </entry>
  <entry>
    <id>2</id>
    <updated>2011-01-18T16:55:54Z</updated>
    <title>title1</title>
  </entry>
  <entry>
    <id>1</id>
    <updated>2011-01-18T16:55:54Z</updated>
    <title>title</title>
  </entry>
</feed>

And I need the outcome to result like:

<feed>
  <entry>
    <id>1</id>
    <updated>2011-01-18T16:55:54Z</updated>
    <title>title</title>
  </entry>
  <entry>
    <id>2</id>
    <updated>2011-01-18T16:55:54Z</updated>
    <title>title1</title>
  </entry>
  <entry>
    <id>3</id>
    <updated>2011-01-18T16:55:54Z</updated>
    <title>title1</title>
  </entry>
  <entry>
    <id>4</id>
    <updated>2011-01-18T16:55:54Z</updated>
    <title>title2</title>
  </entry>
</feed>

Basically I need the XSLT to sort on the title, then the ID. I have made an XSLT but the shorter times come out last (using Xerces):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:atom="http://www.w3.org/2005/Atom"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />

 <xsl:template match="@* | node()">
  <xsl:copy>
   <xsl:apply-templates select="@* | node()" />
  </xsl:copy>
 </xsl:template>

 <xsl:template match="atom:feed">
  <xsl:copy>
   <xsl:apply-templates select="*" />
   <xsl:for-each select="atom:entry">
    <xsl:sort select="string-length(atom:title)" order="descending" />
    <xsl:sort select="atom:title" data-type="text" order="ascending" />
    <xsl:copy-of select="."/>
   </xsl:for-each>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="atom:feed/atom:entry"/>

</xsl:stylesheet>

Solution

  • For your input sample (not actually an Atom feed), this stylesheet:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:template match="@*|node()">
            <xsl:copy>
                <xsl:apply-templates select="@*|node()" />
            </xsl:copy>
        </xsl:template>
        <xsl:template match="feed">
            <xsl:copy>
                <xsl:apply-templates>
                    <xsl:sort select="update" order="descending"/>
                    <xsl:sort select="title"/>
                    <xsl:sort select="id" data-type="number"/>
                </xsl:apply-templates>
            </xsl:copy>
        </xsl:template>
    </xsl:stylesheet>
    

    Output:

    <feed>
        <entry>
            <id>1</id>
            <updated>2011-01-18T16:55:54Z</updated>
            <title>title</title>
        </entry>
        <entry>
            <id>2</id>
            <updated>2011-01-18T16:55:54Z</updated>
            <title>title1</title>
        </entry>
        <entry>
            <id>3</id>
            <updated>2011-01-18T16:55:54Z</updated>
            <title>title1</title>
        </entry>
        <entry>
            <id>4</id>
            <updated>2011-01-18T16:55:54Z</updated>
            <title>title2</title>
        </entry>
    </feed>
    

    Note: This date time format can be ordered like string (default) as long as there is no different time zone.