2

I want to output grouped results in 3 columns. I'm not using a table so I guess I mean 3 sections of results (see the HTML) side-by-side.

I've grouped results according to the value of one of their nodes, and grouped under a heading which is the value of the node. For example:

<All_Results>
  <Result>
   <Dept>Finance</Dept>
   <Name>Bob</Name>
  </Result>
  <Result>
   <Dept>Finance</Dept>
   <Name>Susan</Name>
  </Result>
  <Result>
   <Dept>Sales</Dept>
   <Name>Igor</Name>
  </Result>
</All_Results>

is formatted like:

 <li>
    <h4>Finance</h4>
    <ul>
      <li>Bob</li>
    </ul>
    <ul>
      <li>Susan</li>
    </ul>

    <h4>Sales</h4>
    <ul>
      <li>Igor</li>
    </ul>
 </li>

And this works and I'm happy with it. Now what I'm trying to do is create 3 columns side-by-side of Depts and their results (i.e., Names in this example). I'm expecting 9 possible Depts, but this may change in the future.

Here is my XSLT so far (all it does so far is the above formatting, no work on the columns yet, I'm not sure how to approach this problem):

<xsl:output method="html" />
<xsl:key name="results-by-dept" match="Result" use="Dept" />
<xsl:template match="All_Results">
  <xsl:variable name="Rows" select="Result" />
  <xsl:variable name="RowCount" select="count($Rows)" />
  <ul class="deptList">
    <xsl:for-each select="Result[count(. | key('results-by-dept', Dept)[1]) = 1]">
      <xsl:sort select="Dept"  order ="ascending"/>
      <li>
        <h4>
          <xsl:value-of select="Dept" />
        </h4>
        <ul>
          <xsl:for-each select="key('results-by-dept', Dept)">
            <xsl:sort select="Name" />
            <li>
                <xsl:value-of select="Name"/>
            </li>
          </xsl:for-each>
        </ul>
      </li>
    </xsl:for-each>
  </ul>
</xsl:template>
</xsl:stylesheet>

Any help would be much appreciated!


Sean's edit

After discussion with the OP, I think this Use Case illustrates what he wants. The following sample input document shows 5 departments, with with one or two employees. The ordering of nodes is not significant.

Use case 1: Input document

<All_Results>
    <Result>
        <Dept>Finance</Dept>
        <Name>Bob</Name>
    </Result>
    <Result>
        <Dept>Engineering</Dept>
        <Name>Inna</Name>
    </Result>
    <Result>
        <Dept>Finance</Dept>
        <Name>Susan</Name>
    </Result>
    <Result>
        <Dept>Sales</Dept>
        <Name>Igor</Name>
    </Result>
    <Result>
        <Dept>Human resources</Dept>
        <Name>Jane</Name>
    </Result>
    <Result>
        <Dept>Admin</Dept>
        <Name>Joe</Name>
    </Result>
    <Result>
        <Dept>Engineering</Dept>
        <Name>Dima</Name>
    </Result>
    <Result>
        <Dept>Human resources</Dept>
        <Name>Beth</Name>
    </Result>
</All_Results>

The output is to be transformed like so (following listing). A HTML table is to be constructed with 3 columns but only one row. Each cell is to contain a HTML unordered lists of departmental employees, headed by the department name. Each column is to contain roughly one third of the departments, with the last column being ragged in this respect. Reading top-down and then left-to-right, departments should be sorted alphabetically. Within each department, employees should be listed alphabetically.

Use case 1: Output document

<table>
    <tr>
        <td>
            <ul class="deptList">
                <li>
                    <h4>Admin</h4>
                    <ul>
                        <li>Joe</li>
                    </ul>
                </li>
                <li>
                    <h4>Engineering</h4>
                    <ul>
                        <li>Dima</li>
                        <li>Inna</li>
                    </ul>
                </li>
            </ul>
        </td>
        <td>
            <ul class="deptList">
                <li>
                    <h4>Finance</h4>
                    <ul>
                        <li>Bob</li>
                        <li>Susan</li>
                    </ul>
                </li>
                <li>
                    <h4>Human resources</h4>
                    <ul>
                        <li>Beth</li>
                        <li>Jane</li>
                    </ul>
                </li>
            </ul>
        </td>
        <td>
            <ul class="deptList">
                <li>
                    <h4>Sales</h4>
                    <ul>
                        <li>Igor</li>
                    </ul>
                </li>
            </ul>
        </td>
    </tr>
</table>
Community
  • 1
  • 1
John Smith
  • 31
  • 7

3 Answers3

2

This XSLT 1.0 transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:key name="kDeptByVal" match="Dept" use="."/>
 <xsl:key name="kEmpByDept" match="Name" use="../Dept"/>

 <xsl:variable name="vDeptsUniq" select=
  "/*/*/Dept[generate-id() = generate-id(key('kDeptByVal',.)[1])]"/>

 <xsl:variable name="vDeptsPerCol" select="ceiling(count($vDeptsUniq) div 3)"/>

 <xsl:template match="/*">
  <table border="1">
    <tr>
      <xsl:for-each select="$vDeptsUniq[not(position() > 3)]">
        <xsl:variable name="vCol" select="position()"/>
        <td>
          <ul class="deptList">
           <xsl:for-each select="$vDeptsUniq">
            <xsl:sort/>
            <xsl:if test=
             "position() > ($vCol -1)*$vDeptsPerCol
             and
              not(position() > $vCol*$vDeptsPerCol)
             ">
                <li>
                      <h4><xsl:value-of select="."/></h4>
                    <ul>
                      <xsl:apply-templates select="key('kEmpByDept', .)">
                       <xsl:sort/>
                      </xsl:apply-templates>
                    </ul>
                </li>
            </xsl:if>
           </xsl:for-each>
          </ul>
        </td>
      </xsl:for-each>
    </tr>
  </table>
 </xsl:template>

 <xsl:template match="Name">
  <li><xsl:value-of select="."/></li>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<All_Results>
    <Result>
        <Dept>Finance</Dept>
        <Name>Bob</Name>
    </Result>
    <Result>
        <Dept>Engineering</Dept>
        <Name>Inna</Name>
    </Result>
    <Result>
        <Dept>Finance</Dept>
        <Name>Susan</Name>
    </Result>
    <Result>
        <Dept>Sales</Dept>
        <Name>Igor</Name>
    </Result>
    <Result>
        <Dept>Human resources</Dept>
        <Name>Jane</Name>
    </Result>
    <Result>
        <Dept>Admin</Dept>
        <Name>Joe</Name>
    </Result>
    <Result>
        <Dept>Engineering</Dept>
        <Name>Dima</Name>
    </Result>
    <Result>
        <Dept>Human resources</Dept>
        <Name>Beth</Name>
    </Result>
</All_Results>

produces the wanted, correct result:

<table border="1">
   <tr>
      <td>
         <ul class="deptList">
            <li>
               <h4>Admin</h4>
               <ul>
                  <li>Joe</li>
               </ul>
            </li>
            <li>
               <h4>Engineering</h4>
               <ul>
                  <li>Dima</li>
                  <li>Inna</li>
               </ul>
            </li>
         </ul>
      </td>
      <td>
         <ul class="deptList">
            <li>
               <h4>Finance</h4>
               <ul>
                  <li>Bob</li>
                  <li>Susan</li>
               </ul>
            </li>
            <li>
               <h4>Human resources</h4>
               <ul>
                  <li>Beth</li>
                  <li>Jane</li>
               </ul>
            </li>
         </ul>
      </td>
      <td>
         <ul class="deptList">
            <li>
               <h4>Sales</h4>
               <ul>
                  <li>Igor</li>
               </ul>
            </li>
         </ul>
      </td>
   </tr>
</table>
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
1

Here is one approach in XSLT 1.0:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:key name="depts" match="Dept" use="." />
  <xsl:key name="results-by-dept" match="Result" use="Dept" />

  <xsl:template match="All_Results">
    <xsl:variable name="max-cols" select="3"/>
    <xsl:variable name="depts"
                  select="Result/Dept[count(.|key('depts', .)[1]) = 1]"/>
    <xsl:variable name="col-size"
                  select="ceiling(count($depts) div $max-cols)"/>
    <table>
      <tr>
        <xsl:for-each select="$depts[(position() - 1) mod $col-size = 0]">
          <xsl:variable name="col" select="position() - 1"/>
          <td>
            <ul class="deptList">
              <xsl:for-each select="$depts">
                <xsl:sort select="."/>
                <xsl:if test="floor((position() - 1) div $col-size) = $col">
                  <li>
                    <xsl:apply-templates select="."/>
                  </li>
                </xsl:if>
              </xsl:for-each>
            </ul>
          </td>
        </xsl:for-each>
      </tr>
    </table>
  </xsl:template>


  <xsl:template match="Dept">
    <h4>
      <xsl:value-of select="." />
    </h4>
    <ul>
      <xsl:for-each select="key('results-by-dept', .)">
        <xsl:sort select="Name" />
        <li>
          <xsl:value-of select="Name"/>
        </li>
      </xsl:for-each>
    </ul>
  </xsl:template>

</xsl:stylesheet>

Some notes:

  • The main trick here is to use $depts[(position() - 1) mod $col-size = 0] to loop as many times as there will be columns.
  • An alternative way to loop through the columns would be to use a recursive template and increment a counter variable.
  • This would be a lot easier in XSLT 2.0.
  • The count(.|key('depts', .)[1]) = 1 predicate in Muenchian grouping could be equivalently written as generate-id() = generate-id(key('depts', .)[1]). I prefer the latter since I think it is less obscure, but I have used the former here since it is what was used in the question.
Jukka Matilainen
  • 9,608
  • 1
  • 25
  • 19
1

This XSLT 1.0 style-sheet will give you 3 columns ...

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="kDepartments" match="Result" use="Dept" />

<xsl:template match="/">
 <table>
  <tr>
   <xsl:variable name="dept-count" select="count( */Result[
           generate-id(.) = generate-id( key('kDepartments',Dept)[1])])" />
   <xsl:for-each select="(//*)[position() &lt;= 3]">
     <xsl:variable name="column" select="position()" />
     <td>
       <ul class="deptList">
         <xsl:for-each select="/*/Result[
           generate-id(.) = generate-id( key('kDepartments',Dept)[1])]" >
            <xsl:sort select="Dept" />
            <xsl:variable name="DeptIndex" select="position()" /> 
            <xsl:apply-templates select="self::Result[
              (floor((($DeptIndex - 1)*3 div $dept-count)) + 1) = $column]" /> 
         </xsl:for-each>    
       </ul>  
     </td>  
   </xsl:for-each>  
  </tr>
 </table>
</xsl:template>

<xsl:template match="Result">
 <h4>
  <xsl:value-of select="Dept" />
 </h4>
    <ul>
      <xsl:for-each select="key('kDepartments', Dept)">
        <xsl:sort select="Name" />
        <li>
          <xsl:value-of select="Name"/>
        </li>
      </xsl:for-each>
    </ul>
</xsl:template>

</xsl:stylesheet>
Sean B. Durkin
  • 12,659
  • 1
  • 36
  • 65