user944513
user944513

Reputation: 12729

how to use xsl:sort in xslt?

I have an XML file containing two <video> elements. My transform merges these into a single <videos> element via a for-each, recording the result as the value of variable variableA.

Can I cause the transformed contents to be sorted based on the values of the <v> elements in the original data?

here is my code http://xsltransform.net/pNmBxZH/8

Input xml

<test>
    <video graph="1" potime="1811">
        <sec>
            <secid>3812907</secid>
            <pick>
                <vdsecname>A</vdsecname>
                <mediadate>24 Apr 2017, 7:45PM IST</mediadate>
                <v>240420171945</v>

            </pick>
            <pick>
                <vdsecname>D</vdsecname>
                <mediadate>20 Apr 2017, 4:30PM IST</mediadate>
                <v>200420171630</v>
            </pick>
            <pick>
                <vdsecname>E</vdsecname>
                <mediadate>20 Apr 2017, 3:30PM IST</mediadate>
                 <v>200420171530</v>
            </pick>

        </sec>
    </video>
    <video graph="1" potime="94">
        <sec>
            <secid>20970242</secid>
            <pick>
                <vdsecname>B</vdsecname>
                <mediadate>23 Apr 2017, 7:30PM IST</mediadate>
                 <v>230420171930</v>
            </pick>
            <pick>
                <vdsecname>C</vdsecname>
                <mediadate>22 Apr 2017, 5:33PM IST</mediadate>
                 <v>220420171733</v>
            </pick>
        </sec>
    </video>
</test>

using xsl:sort I want to my variable variableA .There is value in v tag using this value I want to sort my variable

expected output

 <videos>
        <pick>

            <vdsecname>A</vdsecname>

            <mediadate>24 Apr 2017, 7:45PM IST</mediadate>

            <v>240420171945</v>


        </pick>
        <pick>

            <vdsecname>B</vdsecname>

            <mediadate>23 Apr 2017, 7:30PM IST</mediadate>

            <v>230420171930</v>

        </pick>
        <pick>

            <vdsecname>C</vdsecname>

            <mediadate>22 Apr 2017, 5:33PM IST</mediadate>

            <v>220420171733</v>

        </pick>
        <pick>

            <vdsecname>D</vdsecname>

            <mediadate>20 Apr 2017, 4:30PM IST</mediadate>

            <v>200420171630</v>

        </pick>
        <pick>

            <vdsecname>E</vdsecname>

            <mediadate>20 Apr 2017, 3:30PM IST</mediadate>

            <v>200420171530</v>

        </pick>

    </videos>

I want the resulting <pick> elements within the <videos> to be sorted in descending order by their <v> children (which are copied from the input document), as shown.

Here is my current transform:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="html" doctype-public="XSLT-compat" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />

    <xsl:template match="/">
      <hmtl>
        <head>
          <title>New Version!</title>
        </head>
        <xsl:variable name="variableA">
          <videos>

            <xsl:for-each select="test/video">
              <xsl:copy-of select="sec/pick"/>
            </xsl:for-each>
          </videos>
        </xsl:variable>
        <xsl:copy-of select="$variableA"/>
      </hmtl>
    </xsl:template>

</xsl:transform>

Upvotes: 1

Views: 542

Answers (2)

John Bollinger
John Bollinger

Reputation: 180083

You certainly can use xsl:sort to produce the output you want. Inasmuch as you know about that element, yet still ask the question, I'll suppose that you tried to use it but did not get the result you wanted. That would be unsurprising, given the current structure of your stylesheet.

One of the most important things to understand about xsl:sort is what it causes to be sorted, and that is the input nodes selected by the apply-templates or for-each element with which it is associated. xsl-sort causes the input nodes to be processed in the order it describes, with the context's current node list also ordered in that way. That is a problem for your stylesheet as written, because it is the <pick> elements you want to sort, but the <video> elements over which you are iterating.

Additionally, it is unclear why you introduce a variable here. I suspect you have the misapprehension that you might sort the contents of that variable after they have been set, but XSLT does not work that way. If that value were a node set then you could sort while applying a transform to that value. If you're only going to use the variable's value once, however, then you've no need for a variable at all -- you might as well express the transform directly.

Here, then, is a variation on your stylesheet (in XSLT 1.0) that produces the expected output structure:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html" doctype-public="XSLT-compat"
              omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />

  <xsl:template match="/">
    <hmtl>
      <head>
        <title>New Version!</title>
      </head>
      <videos>
        <!-- iterate over (all) the <pick> elements, in sorted order -->
        <xsl:for-each select="test/video/sec/pick">
          <!-- sort keys must come first -->
          <xsl:sort select="v" data-type="number" order="descending" />
          <xsl:copy-of select="."/>
        </xsl:for-each>
      </videos>
    </hmtl>
  </xsl:template>

</xsl:stylesheet>

You can also specify more complex expressions for sort keys, as well as multiple sort keys. In the latter case, the keys are interpreted from most significant to least significant. For example, if your <v> elements are intended to be interpreted as structured digit strings encoding the same dates as the <mediadate> elements, to be sorted in descending order of the dates they represent, then you might substitute this stylesheet fragment in place of the corresponding one above:

        <xsl:for-each select="test/video/sec/pick">
          <!-- sort keys must come first -->
          <xsl:sort select="substring(v, 5, 4)" data-type="number" order="descending" />
          <xsl:sort select="substring(v, 3, 2)" data-type="number" order="descending" />
          <xsl:sort select="substring(v, 1, 2)" data-type="number" order="descending" />
          <xsl:sort select="substring(v, 9, 4)" data-type="number" order="descending" />
          <xsl:copy-of select="."/>
        </xsl:for-each>

Upvotes: 0

John Ernst
John Ernst

Reputation: 1235

May a little tweek to the for-each and sort will help. Let us know.

         <xsl:for-each select="test/video/sec/pick">
            <xsl:sort select="vdsecname" data-type="text" order="ascending"/>
            <xsl:copy-of select="."/>
          </xsl:for-each>

Upvotes: 1

Related Questions