Salida de lista agrupada en 3 columnas usando XSLT

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>

tiene el formato siguiente:

 <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>

Cualquier ayuda sería muy apreciada!


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>

preguntado el 31 de julio de 12 a las 15:07

Please show your expected output with the 3 columns. What type of columns do you mean? Perhaps you mean a HTML table with 3 columns? -

Hi Sean. I just edited the original post at the top. I don't mean in an HTML table so "columns" was misleading. Can I do this without a table? -

What is a "section of results"? -

i.imgur.com/1sgpm.png I'd like to end up with something like this -

So you said you didn't mean a HTML table, and then you show a table? -

3 Respuestas

Esta transformación 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: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>

cuando se aplica en el documento XML proporcionado:

<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>

produce el resultado deseado y correcto:

<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>

Respondido 01 ago 12, 05:08

Dimitre, thanks for submitting this. I'm learning a good deal from the different solutions. - John Smith

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>

Respondido 01 ago 12, 02:08

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>

Algunas notas:

  • 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.
  • La opción count(.|key('depts', .)[1]) = 1 predicate in Agrupación muenchiana podría escribirse de forma equivalente como 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.

Respondido 01 ago 12, 05:08

Thanks Jukka. Your notes are useful! - John Smith

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.