Grouping and sorting XSLT by key and position ()

I am trying to display data sorted alphabetically, so that elements starting with a single letter are in separate columns. These columns can contain no more than 10 elements before starting a new column. I can successfully split the data alphabetically and divide it by the number of elements per column, but I'm struggling to combine 2:

Alphabetically Separated:

<xsl:template match="/"> <xsl:key name="node-by-first-letter" match="node" use="substring(@email, 1, 1)" /> <div class="scroller-panel"> <xsl:for-each select="msxml:node-set($members)/node[count(. | key('node-by-first-letter', substring(@email, 1, 1))[1]) = 1]"> <xsl:sort select="@email" order="ascending"/> <xsl:apply-templates select="." mode="group" /> </xsl:for-each></div></xsl:template> <xsl:template match="node" mode="group"> <div class="column-312 scroller-item people-search-column fade-panel"> <h2> <xsl:value-of select="Exslt.ExsltStrings:uppercase(substring(@email,1,1))"/> </h2> <ul class="stripe-list"> <xsl:apply-templates select="key('node-by-first-letter', substring(@email, 1, 1))" mode="item"> <xsl:sort select="@email" /> </xsl:apply-templates> </ul> </div> </xsl:template> <xsl:template match="node" mode="item"> <li> <a href="4.0.1.person.profile.html"> <xsl:value-of select="@email"/> </a> </li> </xsl:template> 

Divided by the maximum number of elements per column:

 <xsl:for-each select="msxml:node-set($members)/members/member[position() mod 10 = 1]"> <ul> <xsl:for-each select=". | following-sibling::*[not(position() >= 10)]"> <li> <xsl:value-of select="@email"/> </li> </xsl:for-each> </ul> </xsl:for-each> 

The preferred output is as follows:

http://rookery9.aviary.com.s3.amazonaws.com/9676500/9676792_3580_625x625.jpg

+1
source share
1 answer

I. XSLT 2.0 Solution :

 <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:param name="pColLength" as="xs:integer" select="10"/> <xsl:template match="/*"> <names> <xsl:for-each-group select="name" group-by="substring(.,1,1)"> <xsl:sort select="current-grouping-key()"/> <xsl:for-each-group select="current-group()" group-by="(position()-1) idiv $pColLength"> <column> <xsl:copy-of select="current-group()"/> </column> </xsl:for-each-group> </xsl:for-each-group> </names> </xsl:template> </xsl:stylesheet> 

when applied to this XML document (since there was no such question in the question):

 <names> <name>T A</name> <name>T B</name> <name>T C</name> <name>T D</name> <name>T E</name> <name>T F</name> <name>T G</name> <name>T H</name> <name>T I</name> <name>T J</name> <name>T K</name> <name>T L</name> <name>A A</name> <name>A B</name> <name>A C</name> <name>A D</name> <name>A E</name> <name>A F</name> <name>A G</name> <name>A H</name> <name>A I</name> <name>A J</name> <name>A K</name> <name>A L</name> <name>X A</name> <name>X B</name> <name>X C</name> <name>X D</name> <name>X E</name> <name>X F</name> <name>X G</name> <name>X H</name> <name>X I</name> <name>X J</name> <name>X K</name> <name>X L</name> <name>R A</name> <name>R B</name> <name>R C</name> <name>R D</name> <name>R E</name> <name>R F</name> <name>R G</name> <name>R H</name> <name>R I</name> <name>R J</name> <name>R K</name> <name>R L</name> </names> 

creates the desired names sorted by the first letter and placed in columns of 10 elements :

 <names> <column> <name>A A</name> <name>A B</name> <name>A C</name> <name>A D</name> <name>A E</name> <name>A F</name> <name>A G</name> <name>A H</name> <name>A I</name> <name>A J</name> </column> <column> <name>A K</name> <name>A L</name> </column> <column> <name>R A</name> <name>R B</name> <name>R C</name> <name>R D</name> <name>R E</name> <name>R F</name> <name>R G</name> <name>R H</name> <name>R I</name> <name>R J</name> </column> <column> <name>R K</name> <name>R L</name> </column> <column> <name>T A</name> <name>T B</name> <name>T C</name> <name>T D</name> <name>T E</name> <name>T F</name> <name>T G</name> <name>T H</name> <name>T I</name> <name>T J</name> </column> <column> <name>T K</name> <name>T L</name> </column> <column> <name>X A</name> <name>X B</name> <name>X C</name> <name>X D</name> <name>X E</name> <name>X F</name> <name>X G</name> <name>X H</name> <name>X I</name> <name>X J</name> </column> <column> <name>X K</name> <name>X L</name> </column> </names> 

Explanation

  • Nested xsl:for-each-group - are first grouped by the start character, then for each such group, a defined and sorted group - by the number of the column in which its elements should be located.

  • Using the standard XSLT 2.0 functions current-grouping-key() and current-group() .

Solution II.XSLT 1.0 :

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:param name="pColLength" select="10"/> <xsl:key name="kStarting" match="name" use="substring(.,1,1)"/> <xsl:template match="/*"> <names> <xsl:for-each select= "name [generate-id() = generate-id(key('kStarting', substring(.,1,1))[1]) ] "> <xsl:sort select="substring(.,1,1)"/> <xsl:variable name="vgroupNames" select= "key('kStarting', substring(.,1,1))"/> <xsl:apply-templates select="$vgroupNames[1]"> <xsl:with-param name="pGroup" select="$vgroupNames"/> <xsl:with-param name="pGroupLength" select= "count($vgroupNames)"/> </xsl:apply-templates> </xsl:for-each> </names> </xsl:template> <xsl:template match="name"> <xsl:param name="pGroup"/> <xsl:param name="pGroupLength"/> <xsl:param name="pInd" select="1"/> <xsl:if test="not($pInd > $pGroupLength)"> <column> <xsl:copy-of select= "$pGroup [position() >= $pInd and not(position() > $pInd + $pColLength -1) ]"/> </column> <xsl:apply-templates select= "$pGroup[position() = $pInd + $pColLength]"> <xsl:with-param name="pGroup" select="$pGroup"/> <xsl:with-param name="pGroupLength" select="$pGroupLength"/> <xsl:with-param name="pInd" select="$pInd + $pColLength"/> </xsl:apply-templates> </xsl:if> </xsl:template> </xsl:stylesheet> 

if applied to the same XML document (as indicated above), the same desired result is created - the names are sorted by starting the first letter and placed in columns of 10 elements each :

 <names> <column> <name>A A</name> <name>A B</name> <name>A C</name> <name>A D</name> <name>A E</name> <name>A F</name> <name>A G</name> <name>A H</name> <name>A I</name> <name>A J</name> </column> <column> <name>A K</name> <name>A L</name> </column> <column> <name>R A</name> <name>R B</name> <name>R C</name> <name>R D</name> <name>R E</name> <name>R F</name> <name>R G</name> <name>R H</name> <name>R I</name> <name>R J</name> </column> <column> <name>R K</name> <name>R L</name> </column> <column> <name>T A</name> <name>T B</name> <name>T C</name> <name>T D</name> <name>T E</name> <name>T F</name> <name>T G</name> <name>T H</name> <name>T I</name> <name>T J</name> </column> <column> <name>T K</name> <name>T L</name> </column> <column> <name>X A</name> <name>X B</name> <name>X C</name> <name>X D</name> <name>X E</name> <name>X F</name> <name>X G</name> <name>X H</name> <name>X I</name> <name>X J</name> </column> <column> <name>X K</name> <name>X L</name> </column> </names> 

Explanation

  • Using the Muenchian grouping method , as well as sorting, we get (in sorted order) each group of name elements, consisting of all names starting with the same character.

  • Each group of name elements obtained above is processed by applying patterns to its first name element . The entire group, its length and the index of the name element in the group (default = 1) are passed as parameters.

  • The template corresponding to the name element is guaranteed to apply only to the original element within the column . It creates a new column element and copies in it all the name elements for this column (starting with the index $pInd and ending with the index $pInd+$pColLength -1 ). There is no requirement that these elements be siblings (and they were not) t) If each name requires not just copying, but also additional processing, this can be done by replacing the <xsl:copy-of> statement with:

-

 <xsl:apply-templates mode="process" select= "$pGroup [position() >= $pInd and not(position() > $pInd + $pColLength -1) ]"/> 
+4
source

All Articles