XSLT Custom Sort

后端 未结 4 1896
故里飘歌
故里飘歌 2021-01-14 11:15

Is it possible in XSLT to sort in alphabetical order, with 5 items as \"preferred\".

i.e. given




        
相关标签:
4条回答
  • 2021-01-14 11:34

    Have you seen xsl:sort element, you can create two or more sorting criteria by combining xsl:apply-templates and xsl:sort

    Indeeed, here is a sample where some countries comes first, in a predefined order, then all the others. Note that these are elements, not attributes as above.

    <xsl:sort select="not(CountryValue = 'Scotland')"/>
    <xsl:sort select="not(CountryValue = 'India')"/>
    <xsl:sort select="CountryValue"/>
    

    Sample result:

    Country

    Scotland   
    Scotland 
    Scotland 
    India 
    Afghanistan 
    Afghanistan 
    Afghanistan 
    Afghanistan 
    Afghanistan 
    Afghanistan 
    Albania 
    Albania 
    Albania 
    Algeria
    
    0 讨论(0)
  • 2021-01-14 11:41

    In your XSLT, are you able to make use of extension functions?

    If so, one method is to modify the existing list of nodes in-line, to create a new node set but with an extra 'sortname' attribute on each node. You can then iterate through this new node set, sorting using the new 'sortname' attribute:

    <?xml version="1.0"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="urn:schemas-microsoft-com:xslt">
        <xsl:output method="xml" />
        <xsl:template match="/teams">
            <xsl:variable name="extendedteams">
            <xsl:for-each select="team">
                <xsl:copy>
                    <xsl:copy-of select="@*" />
                    <xsl:attribute name="sortname">
                        <xsl:choose>
                            <xsl:when test="@name='England' or @name='Northern Ireland' or @name='Republic of Ireland' or @name='Scotland' or @name='Wales'">1</xsl:when>
                            <xsl:otherwise>2</xsl:otherwise>
                        </xsl:choose>
                        <xsl:value-of select="@name" />
                    </xsl:attribute>
                </xsl:copy>
            </xsl:for-each>
            </xsl:variable>
            <xsl:copy>
            <xsl:for-each select="exsl:node-set($extendedteams)/team">
                <xsl:sort select="@sortname" />
    
                <xsl:copy>
                    <xsl:copy-of select="@*[name() != 'sortname']" />
                </xsl:copy>
            </xsl:for-each>
            </xsl:copy>
        </xsl:template>
    
    </xsl:stylesheet>
    

    In the example above, I prefix a '1' onto any national team, and a '2' on any domestic team, and then sort on this new attribute.

    See information on Node Sets to see what XSLT processers support which extensions.

    0 讨论(0)
  • 2021-01-14 11:42

    You could do this:

    <xsl:stylesheet
      version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:my="http://tempuri.org"
      exclude-result-prefixes="my"
    >
    
      <xsl:output method="xml" indent="yes" /> 
    
      <my:data>
        <my:nationalteams>
          <my:team id="121" /><!-- Republic of Ireland -->
          <my:team id="123" /><!-- England -->
          <my:team id="126" /><!-- Northern Ireland -->
          <my:team id="142" /><!-- Scotland -->
          <my:team id="295" /><!-- Wales -->
        </my:nationalteams>
      </my:data>
    
      <xsl:template match="teams">
        <xsl:copy>
          <xsl:variable name="national" select="
            document('')/*/my:data/my:nationalteams/my:team
          " />
          <!-- national teams preferred -->
          <xsl:apply-templates select="team[@id = $national/@id]">
            <xsl:sort select="@name" />
          </xsl:apply-templates>
          <!-- other teams after them -->
          <xsl:apply-templates select="team[not(@id = $national/@id)]">
            <xsl:sort select="@name" />
          </xsl:apply-templates>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="team">
        <xsl:copy-of select="." />
      </xsl:template>
    
    </xsl:stylesheet>
    

    The whole <my:data> could be moved to a secondary XML/config file, where you can also leave off the "my" namespace.

    After that, one line would need a small change:

    <xsl:variable name="national" select="
      document('config.xml')/data/nationalteams/team
    " />
    

    The output of the above is somewhat unsurprising :-)

    <teams>
      <team id="123" name="England" />
      <team id="126" name="Northern Ireland" />
      <team id="121" name="Republic of Ireland" />
      <team id="142" name="Scotland" />
      <team id="295" name="Wales" />
      <team id="49" name="Arsenal" />
      <team id="299" name="Bolton" />
      <team id="84" name="Chelsea" />
      <team id="110" name="Liverpool" />
      <team id="42" name="Manchester City" />
      <team id="13" name="Manchester United" />
      <team id="298" name="Tottenham Hotspur" />
    </teams>
    
    0 讨论(0)
  • 2021-01-14 11:54

    Tim C has already given a nice answer, but maybe a simpler way will suffice in your case. You could simply specify two xsl:sort conditions: The first will sort by preferred/not-preferred items, the second one alphabetically by name:

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml" indent="yes"/>
    
      <xsl:template match="/teams">
        <teams>
          <xsl:for-each select="team">
            <xsl:sort select="not(@name = 'England' or @name='Northern Ireland'
                               or @name='Republic of Ireland' 
                               or @name='Scotland' or @name='Wales')" 
                      data-type="number"/>
            <xsl:sort select="@name"/>
            <team>
              <xsl:value-of select="@name"/>
            </team>
          </xsl:for-each>
        </teams>
      </xsl:template>
    </xsl:stylesheet>
    

    Please note that you have to invert the first condition using not(). The reason is that the boolean result of your expression is converted to a number (0 is false, 1 is true) and hence the items evaluating to 'false' will be listed first.

    0 讨论(0)
提交回复
热议问题