epifun
epifun

Reputation: 109

XSLT Sorting subnodes

Sorry for my English. I need sort nodes of this XML data

<root>
  <section id="1">
    <news-item id="1" pub-date="2012-01-03" />
    <news-item id="2" pub-date="2012-01-04" />
    <news-item id="3" pub-date="2011-12-21" />
  </section>

  <section id="2">
    <news-item id="4" pub-date="2012-01-05" />
    <news-item id="5" pub-date="2012-01-06" />
    <news-item id="6" pub-date="2012-01-07" />
  </section>

  <section id="3">
    <news-item id="7" pub-date="2012-02-10" />
    <news-item id="8" pub-date="2012-02-11" />
    <news-item id="9" pub-date="2012-02-12" />
  </section>
</root>

to this

<root>
  <section id="3">
    <news-item id="9" pub-date="2012-02-12" />
    <news-item id="8" pub-date="2012-02-11" />
    <news-item id="7" pub-date="2012-02-10" />
  </section>

  <section id="2">
    <news-item id="6" pub-date="2012-01-07" />
    <news-item id="5" pub-date="2012-01-06" />
    <news-item id="4" pub-date="2012-01-05" />
  </section>

  <section id="1">
    <news-item id="2" pub-date="2012-01-04" />
    <news-item id="1" pub-date="2012-01-03" />
    <news-item id="3" pub-date="2011-12-21" />
  </section>
</root>

i.e. I need first sort news-item elements by pub-date in section, and then sort section element by max pub-date in news-item. (Section with lastes news must be on top).

Many thanks!

Upvotes: 1

Views: 301

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243449

An alternative, somewhat simpler, 2-pass solution (no xsl:variable, no generate-id(), no pipe-separated values, but using msxsl:node-set()):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:msxsl="urn:schemas-microsoft-com:xslt"
 exclude-result-prefixes="msxsl">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:variable name="vrtfPass1">
  <xsl:apply-templates mode="pass1"/>
 </xsl:variable>

 <xsl:template match="/*">
  <root>
   <xsl:for-each select=
   "msxsl:node-set($vrtfPass1)/*/*">
     <xsl:sort select="*[1]/@pub-date" order="descending"/>
       <xsl:copy-of select="."/>
   </xsl:for-each>
  </root>
 </xsl:template>

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

 <xsl:template match="section" mode="pass1">
  <section id="{@id}">
   <xsl:apply-templates select="*" mode="pass1">
    <xsl:sort select="@pub-date" order="descending"/>
   </xsl:apply-templates>
  </section>
 </xsl:template>
</xsl:stylesheet>

when applied on the following XML document (based on the provided, but made more "interesting):

<root>
    <section id="1">
        <news-item id="1" pub-date="2012-01-03" />
        <news-item id="2" pub-date="2012-01-04" />
        <news-item id="3" pub-date="2011-12-21" />
    </section>
    <section id="2">
        <news-item id="4" pub-date="2012-01-05" />
        <news-item id="5" pub-date="2012-01-06" />
        <news-item id="6" pub-date="2012-01-07" />
        <news-item id="7" pub-date="2222-12-22" />
    </section>
    <section id="3">
        <news-item id="7" pub-date="2012-02-10" />
        <news-item id="8" pub-date="2012-02-11" />
        <news-item id="9" pub-date="2012-02-12" />
    </section>
</root>

the wanted, correct result is produced:

<root>
   <root>
      <section id="2">
         <news-item id="7" pub-date="2222-12-22"/>
         <news-item id="6" pub-date="2012-01-07"/>
         <news-item id="5" pub-date="2012-01-06"/>
         <news-item id="4" pub-date="2012-01-05"/>
      </section>
      <section id="3">
         <news-item id="9" pub-date="2012-02-12"/>
         <news-item id="8" pub-date="2012-02-11"/>
         <news-item id="7" pub-date="2012-02-10"/>
      </section>
      <section id="1">
         <news-item id="2" pub-date="2012-01-04"/>
         <news-item id="1" pub-date="2012-01-03"/>
         <news-item id="3" pub-date="2011-12-21"/>
      </section>
   </root>
</root>

Upvotes: 0

Lucero
Lucero

Reputation: 60190

This is your pastebin with the names fixed and a different sort for the sections:

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

<xsl:template match="section">
    <xsl:copy>
        <xsl:apply-templates select="@*"/>
        <xsl:apply-templates select="news-item">
            <xsl:sort select="@pub-date" data-type="text" order="descending" />
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<xsl:template match="root">
    <xsl:copy>
        <xsl:apply-templates select="@*"/>
        <xsl:variable name="sectionOrder">
            <xsl:text>|</xsl:text>
            <xsl:for-each select="section/news-item">
                <xsl:sort select="@pub-date" data-type="text" order="descending" />
                <xsl:value-of select="generate-id(..)"/>
                <xsl:text>|</xsl:text>
            </xsl:for-each>
        </xsl:variable>
        <xsl:apply-templates select="section">
            <xsl:sort select="string-length(substring-before($sectionOrder, concat('|',generate-id(),'|')))" data-type="number" />
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

The names in your pastebin don't match (newsitem vs. news-item, pubdate vs. pub-date), so I fixed that. The output I get seems to be right (also with the test case you added in your comment):

<root>
    <section id="2">
        <news-item id="7" pub-date="2222-12-22" />
        <news-item id="6" pub-date="2012-01-07" />
        <news-item id="5" pub-date="2012-01-06" />
        <news-item id="4" pub-date="2012-01-05" />
    </section>
    <section id="3">
        <news-item id="10" pub-date="2012-02-12" />
        <news-item id="9" pub-date="2012-02-11" />
        <news-item id="8" pub-date="2012-02-10" />
    </section>
    <section id="1">
        <news-item id="2" pub-date="2012-01-04" />
        <news-item id="1" pub-date="2012-01-03" />
        <news-item id="3" pub-date="2011-12-21" />
    </section>
</root>

Upvotes: 2

Related Questions