tags:

views:

340

answers:

3

I have an input XML file which I need to copy 1:1 to the output, except for one subelement which contains subitems that need to be sorted.

<?xml version="1.0"?>
<top>
  <elementA />
  <elementB />
  <contents>
      <contentitem>
          <id>3</id>
          <moretags1 />
          <moretags2 />
      </contentitem>
      <contentitem>
          <id>2</id>
          <moretags1 />
          <moretags2 />
      </contentitem>
      <contentitem>
          <id>1</id>
          <moretags1 />
          <moretags2 />
      </contentitem>
  </contents>
</top>

I'd like an XSL Transformation that puts the "contentitem" elements in order, sorted by their "id" elements. All other tags, including nested ones, must be copied verbatim. I already tried with xsl:copy, but either I get double contents or something turns out missing.

A: 

Use xsl:sort with either xsl:for-each or xsl:apply-templates

Something like

<xsl:template match='/top/contents'>
  <xsl:apply-templates select='contentitem'>
    <xsl:sort select='id' data-type='number'/>
  </xsl:apply-templates>
</xsl:template>

Multiple xsl:sort elements can be used for multiple sort keys.

Richard
Note you've not written the <contents> element, nor any attributes on it...
Marc Gravell
This is not a complete solution, instead focused on how to sort.
Richard
+2  A: 

How about:

<?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" indent="yes"/>
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="/top/contents">
    <contents>
      <xsl:copy>
        <xsl:apply-templates select="@*"/>
        <xsl:apply-templates select="contentitem">
          <xsl:sort data-type="number" select="id"/>
        </xsl:apply-templates>
      </xsl:copy>
    </contents>
  </xsl:template>
</xsl:stylesheet>
Marc Gravell
+3  A: 

Mark Gravell's solution is almost correct -- with a slight issue that creates two nested <contents> elements. Note to all who provide answers: Do test your solutions!

Here is a correct solution. 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:strip-space elements="*"/>
<!--                                                --> 
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>
<!--                                                --> 
  <xsl:template match="contents">
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <xsl:apply-templates select="contentitem">
        <xsl:sort select="id" data-type="number"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

when applied on the originally-provided XML document:

<top>
    <elementA />
    <elementB />
    <contents>
     <contentitem>
      <id>3</id>
      <moretags1 />
      <moretags2 />
     </contentitem>
     <contentitem>
      <id>2</id>
      <moretags1 />
      <moretags2 />
     </contentitem>
     <contentitem>
      <id>1</id>
      <moretags1 />
      <moretags2 />
     </contentitem>
    </contents>
</top>

produces the wanted, correct result:

<top>
   <elementA/>
   <elementB/>
   <contents>
      <contentitem>
         <id>1</id>
         <moretags1/>
         <moretags2/>
      </contentitem>
      <contentitem>
         <id>2</id>
         <moretags1/>
         <moretags2/>
      </contentitem>
      <contentitem>
         <id>3</id>
         <moretags1/>
         <moretags2/>
      </contentitem>
   </contents>
</top>

Do note the following:

  1. The use of the identity rule to copy all nodes without change.

  2. How the identity template is overriden with a specific template matching the contents element

  3. The use of the <xsl:sort> instruction to present the results of applying a template in a specific order, possibly different from the document order of the nodes, selected for processing.

Dimitre Novatchev