Lukasz
Lukasz

Reputation: 7662

xslt sort output xml

I'm trying to find a solution to the following problem.

I'm developing XSLT transformation (which is now about 40KB big) that is transforming quite complex XMLs into a quite simple structure which would like this:

<Records>
<Record key="XX">
</Record> 
<Record key="XX1">
</Record>
<Record key="XX2">
</Record>
<Record key="XX3">
</Record>
</Records>

I would like to have this output XML sorted according to Records/Record/@key values. The problem is that my XSLT produces this output unsorted and due to its complexity I am unable to sort it there. Is it possible to apply xsl:sort on the output XML? I know that I can prepare another XSLT transform, but in my case that's not the solution, as I'm limited to only one XSLT.. Please, help!...

Upvotes: 3

Views: 3697

Answers (3)

decates
decates

Reputation: 3535

As an addendum to Dimitre's excellent solution above, if you're using an XSLT 1.0 processor (for example, .NET), the following can give you a pointer as to how to use node-set: http://www.xml.com/pub/a/2003/07/16/nodeset.html#tab.namespaces

In my case, I was in .NET 1.1 (i.e. MSXML) and the solution looked something like:

<?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">
    <xsl:template match="/">            
        <xsl:variable name="vrtfPass1">
            <Records xmlns="">  
                    <xsl:apply-templates />
            </Records >            
        </xsl:variable>
        <xsl:variable name="vPass1" select="msxsl:node-set($vrtfPass1)"/>
        <xsl:apply-templates select="$vPass1/*" mode="sorting"/>
    </xsl:template>
    <xsl:template match="Records" mode="sorting">
        <Records>
       <xsl:for-each select="Record">
        <xsl:sort select="@key"/>
        <xsl:copy-of select="."/>
       </xsl:for-each>
      </Records>
    </xsl:template>
</xsl:stylesheet>

Upvotes: 1

Michael Kay
Michael Kay

Reputation: 163262

40Kb is a lot of code for one stylesheet. When things get to this kind of scale, it's usually best to split a transformation into a pipeline of smaller transformations. If you have such a pipeline architecture, then adding a sort step at the end is trivial. There are plenty of technologies for managing a pipeline of transformations (XProc, Orbeon, xmlsh, ant, Coccoon) depending on your requirements. The benefit of pipelining is that it keeps your code modular and reusable.

Upvotes: 3

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243459

Is it possible to apply xsl:sort on the output XML?

Yes, multipass processing is possible, and especially in XSLT 2.0 you don't even need to apply an xxx:node-set() extension on the result, because the infamous RTF type does no longer exist:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
  <xsl:variable name="vPass1">
   <!--
        Put/Invoke your cirrent code here   
        to generate the following           
-->
    <Records>
      <Record key="XX3">
      </Record>
      <Record key="XX2">
      </Record>
      <Record key="XX4">
      </Record>
      <Record key="XX1">
      </Record>
    </Records>
  </xsl:variable>

  <xsl:apply-templates select="$vPass1/*"/>
 </xsl:template>

 <xsl:template match="Records">
  <Records>
   <xsl:perform-sort select="*">
    <xsl:sort select="@key"/>
   </xsl:perform-sort>
  </Records>
 </xsl:template>
</xsl:stylesheet>

When this transformation is performed on any XML document (not used/ignored), the wanted, correct, sorted result is produced:

<Records>
   <Record key="XX1"/>
   <Record key="XX2"/>
   <Record key="XX3"/>
   <Record key="XX4"/>
</Records>

In XSLT 1.0 it is almost the same with the additional conversion of the result from RTF type to a normal tree:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common"
 exclude-result-prefixes="ext">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
  <xsl:variable name="vrtfPass1">
   <!--
        Put/Invoke your cirrent code here   
        to generate the following           
-->
    <Records>
      <Record key="XX3">
      </Record>
      <Record key="XX2">
      </Record>
      <Record key="XX4">
      </Record>
      <Record key="XX1">
      </Record>
    </Records>
  </xsl:variable>

  <xsl:variable name="vPass1"
                select="ext:node-set($vrtfPass1)"/>

  <xsl:apply-templates select="$vPass1/*"/>
 </xsl:template>

 <xsl:template match="Records">
  <Records>
   <xsl:for-each select="*">
    <xsl:sort select="@key"/>

    <xsl:copy-of select="."/>
   </xsl:for-each>
  </Records>
 </xsl:template>
</xsl:stylesheet>

Upvotes: 3

Related Questions