Creating alphabetical index with fixed number of c

2019-08-30 02:22发布

问题:

I have an XML like this:

<xml version="1.0" encoding="UTF-8"?>
<countries>
    <country>
       <name>Latvia</name>   
    </country>
    <country>
       <name>USA</name>
    </country>
    <country>
       <name>Australia</name>
    </country>
    <country>
       <name>Indonesia</name>
    </country>
    <country>
       <name>UK</name>
    </country>
    <country>
       <name>India</name>
    </country>
    <country>
       <name>Argentina</name>
    </country>
    <country>
       <name>Chile</name>
    </country>
    <country>
       <name>Singapore</name>
    </country>
    <country>
       <name>New Zeland</name>
    </country>
    <country>
       <name>Kenya</name>
    </country>
    <country>
       <name>Zambia</name>
    </country>
    <country>
       <name>Tunisia</name>
    </country>
</countries>

Now I want to create alphabetical index of countries in 3 columns. Each column will contain the number of alpha as one third of the starting alphabets present and its corresponding countries.

Last column can have the rest of them if number of starting alphabets present happens to be not divisible by 3.

For example, here we have country names starting with L, C, U, A, I, S, N, K, Z and T.

After arranging: A C I K L N S T U Z

Now, my index will have:

         Column1: A, C and I countries

         Column2: K, L and N countries

         Column3: S, T, U and Z countries

Hence the desired output is:

<countries>
  <column1> 
     <A>
        <country>
           <name>Argentina</name>
        </country>
        <country>
          <name>Australia</name>
        </country>
     </A>
     <C>
        <country>
           <name>Chile</name>
        </country>
     </C>
     <I>
        <country>
           <name>India</name>
        </country>
        <country>
          <name>Indonesia</name>
        </country>
     </I>   
  </column1>

  <column2> 
     <K>
        <country>
           <name>Kenya</name>
        </country>      
     </K>
     <L>
        <country>
           <name>Latvia</name>
        </country>

     </L>
     <N>
        <country>
           <name>New Zeland</name>
        </country>
     </N>   
  </column2>

  <column3> 
     <S>
        <country>
           <name>Singapore</name>
        </country>      
     </S>
     <T>
        <country>
           <name>Tunisia</name>
        </country>

     </T>
     <U>
        <country>
           <name>UK</name>
        </country>
        <country>
           <name>USA</name>
        </country>
     </U>   
     <Z>
        <country>
           <name>Zambia</name>
        </country>
     </Z>   
  </column3>
</countries>

Please help. I am using xslt 2.0.

回答1:

I came up with

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

<xsl:output indent="yes"/>

<xsl:param name="size" select="3"/>

<xsl:template match="countries">
    <xsl:copy>
      <xsl:variable name="groups">
        <xsl:for-each-group select="country" group-by="substring(name, 1, 1)">
            <xsl:sort select="current-grouping-key()"/>
            <xsl:element name="{current-grouping-key()}">
                <xsl:copy-of select="current-group()"/>
            </xsl:element>            
        </xsl:for-each-group>
      </xsl:variable>
      <xsl:for-each select="$groups/*[position() mod $size eq 1]">
        <xsl:if test="position() le $size">
          <xsl:variable name="pos" select="position()"/>
          <xsl:element name="column{position()}">
            <xsl:copy-of select="., following-sibling::*[if ($pos eq $size) then true() else (position() lt $size)]"/>
          </xsl:element>
        </xsl:if>
      </xsl:for-each>
    </xsl:copy>
</xsl:template>


</xsl:transform>


回答2:

I would consider doing this in two passes, first sort and group by letter, giving a sequence of A - Z elements, then partition this sequence into columns.

<xsl:variable name="groups" as="element()*">
  <xsl:for-each-group select="/countries/country" group-by="substring(name, 1, 1)">
    <!-- sort groups alphabetically -->
    <xsl:sort select="current-grouping-key()" />
    <xsl:element name="{current-grouping-key()}">
      <xsl:perform-sort select="current-group()">
        <!-- sort names within each group -->
        <xsl:sort select="name" />
      </xsl:perform-sort>
    </xsl:element>
  </xsl:for-each-group>
</xsl:variable>

<xsl:variable name="numPerCol" select="count($groups) div 3" />
<column1>
  <xsl:sequence select="$groups[position() le $numPerCol]"/>
</column1>
<column2>
  <xsl:sequence select="$groups[position() gt $numPerCol and position() le (2*$numPerCol)]" />
</column2>
<column3>
  <xsl:sequence select="$groups[position() gt (2*$numPerCol)]" />
</column3>