Search code examples
xmlxslt-2.0

XSLT sort child nodes under different parent nodes


I'm trying to sort xml with xslt but the problem is that my code only sort elements under specific node, here is a sample xml:

<region name="Germany">
    <company name="Mercedes" rank="2" />
    <company name="BMW" rank="3" />
</region>
<region name="Japan">
    <company name="Toyota" rank="1" />
    <company name="Mazda" rank="4" />
</region>

I tried the folowing but it didn't work

<xsl:template match="region">
    <Companies>
        <xsl:for-each select="company">
            <xsl:sort select="@rank" />
            <xsl:call-template name="companies">
        </xsl:for-each>
    </Companies>
</xsl:template>

<xsl:template name="companies">
     <Company>
        <xsl:value-of select="@name" />
     </Company>
</xsl:template>

the output should be:

<Companies>
    <Company>Toyota</Company>
    <Company>Mercedes</Company>
    <Company>BMW</Company>
    <Company>Mazda</Company>
</Companies>

Solution

  • You haven't shown any container element for those region elements but assuming you have them inside one common container write a template matching that container (e.g. named root in the sample code below) and then I would simply suggest to apply-templates to the company grandchildren with an xsl:sort included based on the rank attribute.

    Then add a template transforming from the attribute based company input elements to a value based element and you are done:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        exclude-result-prefixes="#all"
        version="3.0">
    
      <xsl:output method="xml" indent="yes"/>
    
      <xsl:template match="root">
          <Companies>
              <xsl:apply-templates select="region/company">
                  <xsl:sort select="xs:integer(@rank)"/>
              </xsl:apply-templates>
          </Companies>
      </xsl:template>
    
      <xsl:template match="company">
          <xsl:copy>
              <xsl:value-of select="@name"/>
          </xsl:copy>
      </xsl:template>
    
    </xsl:stylesheet>
    

    https://xsltfiddle.liberty-development.net/94rmq6B

    Matching on each region doesn't seem to make sense if you want to process them all together in a sorted way.