4

I have a following XML

<Root> 
  <Element A/>
  <Element B/>
  <Data1> 
    <DataElement/> 
    <Values>
       <Value>2222</Value> 
       <Name>field1</Name>
    </Values> 
    <Values>
       <Value>ABC</Value> 
       <Name>field2</Name>
    </Values> 
  </Data1> 
  <Data2> 
    <DataElement/> 
    <Values>
       <Value>1111</Value> 
       <Name>field1</Name>
    </Values> 
    <Values>
       <Value>XYZ</Value> 
       <Name>field2</Name>
    </Values> 
  </Data2>
  <DataN> 
    ... 
  </DataN> 
</Root> 

I need to get the same XML with Data section sorted by "Value" of specified field name, for exmple: sort by "field1" will return

    <Root> 
  <Element A/>
  <Element B/>
  <Data2> 
    <DataElement/> 
    <Values>
       <Value>1111</Value> 
       <Name>field1</Name>
    </Values> 
    <Values>
       <Value>XYZ</Value> 
       <Name>field2</Name>
    </Values> 
  </Data2>
  <Data1> 
    <DataElement/> 
    <Values>
       <Value>2222</Value> 
       <Name>field1</Name>
    </Values> 
    <Values>
       <Value>ABC</Value> 
       <Name>field2</Name>
    </Values> 
  </Data1>   
  <DataN> 
    ... 
  </DataN> 
</Root> 

Also, I have to send name of sort field as parameter...

Kirill Polishchuk
  • 54,804
  • 11
  • 122
  • 125
HelenD
  • 41
  • 2

2 Answers2

2

This transformation implements exactly the stated requirements. It takes special care to preserve the exact order of the elements that are not to be sorted. No other answer at present does this:

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

 <xsl:param name="vField" select="'field1'"/>
 <xsl:param name="pSortType" select="'number'"/>

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

   <xsl:template match="*[starts-with(name(),'Data')]">
    <xsl:variable name="vPos" select=
      "count(preceding-sibling::*[starts-with(name(),'Data')])+1"/>

  <xsl:for-each select="/*/*[starts-with(name(),'Data')]">
   <xsl:sort select="Values[Name=$vField]/Value"
             data-type="{$pSortType}"/>
   <xsl:if test="position() = $vPos">
    <xsl:copy-of select="."/>
   </xsl:if>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

When applied on the following XML document (the same as the provided one, but with an additional <Element C=""/> inserted between Data1 and Data2 so that we can verify the preservation of ordering of the non-sorted elements):

<Root>
    <Element A=""/>
    <Element B=""/>
    <Data1>
        <DataElement/>
        <Values>
            <Value>2222</Value>
            <Name>field1</Name>
        </Values>
        <Values>
            <Value>ABC</Value>
            <Name>field2</Name>
        </Values>
    </Data1>
        <Element C=""/>
    <Data2>
        <DataElement/>
        <Values>
            <Value>1111</Value>
            <Name>field1</Name>
        </Values>
        <Values>
            <Value>XYZ</Value>
            <Name>field2</Name>
        </Values>
    </Data2>
</Root>

produces the wanted, correct result -- note that the position of <Element C=""/> is preserved:

<Root>
   <Element A=""/>
   <Element B=""/>
   <Data2>
      <DataElement/>
      <Values>
         <Value>1111</Value>
         <Name>field1</Name>
      </Values>
      <Values>
         <Value>XYZ</Value>
         <Name>field2</Name>
      </Values>
   </Data2>
   <Element C=""/>
   <Data1>
      <DataElement/>
      <Values>
         <Value>2222</Value>
         <Name>field1</Name>
      </Values>
      <Values>
         <Value>ABC</Value>
         <Name>field2</Name>
      </Values>
   </Data1>
</Root>
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
0

I think you can go with this simple transform:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:param name="field" select="'field1'"/>

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

    <xsl:template match="Root">
        <xsl:copy>
            <xsl:apply-templates select="@*|*">
                <xsl:sort select="Values[Name=$field]/
                    Value[string(number(.))!='NaN']" 
                    data-type="number"/>
                <xsl:sort select="Values[Name=$field]/
                    Value[string(number(.))='NaN']"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

When applied to your input (corrected to make it well-formed):

<Root> 
    <Element A=""/>
    <Element B=""/>
    <Data1> 
        <DataElement/> 
        <Values>
            <Value>2222</Value> 
            <Name>field1</Name>
        </Values> 
        <Values>
            <Value>ABC</Value> 
            <Name>field2</Name>
        </Values> 
    </Data1> 
    <Data2> 
        <DataElement/> 
        <Values>
            <Value>1111</Value> 
            <Name>field1</Name>
        </Values> 
        <Values>
            <Value>XYZ</Value> 
            <Name>field2</Name>
        </Values> 
    </Data2> 
</Root>

produces:

<Root>
    <Element A=""></Element>
    <Element B=""></Element>
    <Data2>
        <DataElement></DataElement>
        <Values>
            <Value>1111</Value>
            <Name>field1</Name>
        </Values>
        <Values>
            <Value>XYZ</Value>
            <Name>field2</Name>
        </Values>
    </Data2>
    <Data1>
        <DataElement></DataElement>
        <Values>
            <Value>2222</Value>
            <Name>field1</Name>
        </Values>
        <Values>
            <Value>ABC</Value>
            <Name>field2</Name>
        </Values>
    </Data1>
</Root>

Explanation:

  • Use of identity rule to copy every thing as is.
  • override the required DataN elements and simple application of sort condition based on $field input parameter.
  • data-type for sorting dynamically changes according to element field type
Emiliano Poggi
  • 24,390
  • 8
  • 55
  • 67
  • This transformation doesn't produce the wanted output at all! -1. – Dimitre Novatchev Sep 12 '11 at 12:49
  • @Dimitre, sorry for the incovenience. I've now performed the correct tests and provided a fix to my solution. The approach is still the same. – Emiliano Poggi Sep 12 '11 at 13:24
  • @_empo: I see. Try your solution with the XML document in my answer and see whether the element ordering is preserved (hint: it isn't). – Dimitre Novatchev Sep 12 '11 at 13:46
  • Thaks for your explanations, I changed element name from "Data1(2...N)" to "Data" only, cancelled a sort field as parameter, and sorted by Data/Values/Value. Its good enough for me. – HelenD Oct 16 '11 at 12:13