XSLT: How to generate an HTML table with 3 cells p

2019-07-22 00:19发布

问题:

I generated an HTML table with 2 cells per row following instructions according to this post from StackOverflow

But I want to distribute my data in an HTML table with three cells per row. I changed the numbers for sibling and the calculation for position in the XSLT stylesheet from the post but it doesn't seem to work.

This is my XML source:

<?xml version="1.0" encoding="UTF-8"?>
<report>
    <frontmatter>
        <title>Relatório da 4ª Sessão Intercalar</title>
        <subtitle>Projecto Integrado de Engenharia de Linguagens</subtitle>

        <authors>
            <author>
                <name>Marina Mac</name>
                <nident>pg999</nident>
                <email>pg999@minho.pt</email>
                <url>https://www.linkedin.com</url>
                <affil>Universidade do Minho</affil>
                <photo>source/img/authors/marina.png</photo>
            </author>
            <author>
                <name>Nuno Vie</name>
                <nident>pg998</nident>
                <email>pg998@minho.pt</email>
                <url>https://www.linkedin.com</url>
                <photo>source/img/authors/nuno.jpg</photo>
                <affil>Universidade do Minho</affil>
            </author>
            <author>
                <name>Damien Va</name>
                <nident>pg997</nident>
                <photo>source/img/authors/damien.jpg</photo>
                <url>https://www.linkedin.com</url>
                <email>pg997@minho.pt</email>
                <affil>Universidade do Minho</affil>
            </author>
            <author>
                <name>Tiago Mach</name>
                <nident>pg996</nident>
                <email>pg996@minho.pt</email>
                <url>https://www.linkedin.com</url>
                <affil>Universidade do Minho</affil>
                <photo>source/img/authors/marina.png</photo>
            </author>
            <author>
                <name>Manuel Vie</name>
                <nident>pg995</nident>
                <email>pg995@minho.pt</email>
                <url>https://www.linkedin.com</url>
                <photo>source/img/authors/nuno.jpg</photo>
                <affil>Universidade do Minho</affil>
            </author>
            <author>
                <name>Damien Vim</name>
                <nident>pg994</nident>
                <photo>source/img/authors/damien.jpg</photo>
                <url>https://www.linkedin.com</url>
                <email>pg994@alunos.uminho.pt</email>
                <affil>Universidade do Minho</affil>
            </author>
        </authors>
    </frontmatter>
</report>

And this is my XSLT code which does not do what I want:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs" version="2.0">

    <xsl:output indent="yes"/>

    <xsl:template match="/report">
        <html>
            <head>
                <meta charset="utf-8"/>
                <title><xsl:value-of select="frontmatter/title"/></title>
            </head>
            <body>
                <xsl:apply-templates select="frontmatter"/>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="frontmatter" >
        <table width="100%">
            <tr align="center">
                <td>
                    <h1><xsl:value-of select="title"/></h1>
                </td>
            </tr>
            <xsl:if test="subtitle != '' ">
                <tr align="center">
                    <td>
                        <xsl:value-of select="subtitle"/>
                    </td>
                </tr>
            </xsl:if>
        </table>
        <hr width="90%"/>
        <h2>Autores</h2>
        <table width="90%" align="center">
            <xsl:apply-templates select="authors/author[position() mod 2 = 1]"/>
        </table>
    </xsl:template>

    <xsl:template match="authors/author">
        <tr>
            <xsl:for-each select=". | following-sibling::author[1]" >
                <td>
                    <p><xsl:value-of select="name"></xsl:value-of></p>
                </td>
            </xsl:for-each>
            <xsl:if test="not(following-sibling::author)">
                <td/>
                <td/>
            </xsl:if>
        </tr>
    </xsl:template>

</xsl:stylesheet>

How could I fix my stylesheet so that it generates an HTML table with 3 cells per row?

回答1:

I'm assuming that you expect a result like this, if you have 6 authors:

  <table width="90%" align="center" border="1">
     <tr>
        <td>Marina Machado</td>
        <td>Damien Vaz</td>
        <td>Manuel Vieira</td>
     </tr>
     <tr>
        <td>Nuno Vieira</td>
        <td>Tiago Machado</td>
        <td>Damien Vaz</td>
     </tr>
  </table>

And that if you have three authors or more you will always have three columns. The only case where you'll have less than three columns is if you have zero, one or two authors. The columns will be filled from left to right, so if you have 7 authors, the last row will only have one author in the first column.

This would be the result with five authors:

  <table width="90%" align="center" border="1">
     <tr>
        <td>Marina Machado</td>
        <td>Damien Vaz</td>
        <td>Manuel Vieira</td>
     </tr>
     <tr>
        <td>Nuno Vieira</td>
        <td>Tiago Machado</td>
     </tr>
  </table>

And if you have seven, a new row will be created:

  <table width="90%" align="center" border="1">
     <tr>
        <td>Marina Machado</td>
        <td>Damien Vaz</td>
        <td>Manuel Vieira</td>
     </tr>
     <tr>
        <td>Nuno Vieira</td>
        <td>Tiago Machado</td>
        <td>William Shakespeare</td>
     </tr>
     <tr>
        <td>Heitor Villa-Lobos</td>
     </tr>
  </table>

You can generate a HTML table that satisfies those requirements, given your source XML, with this stylesheet (I reduced it to the essential parts of your code to deal with the grouping problem - you can later readapt it to your code):

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

    <xsl:param name="cols">3</xsl:param> <!-- set the number of rows here -->

    <xsl:template match="frontmatter" >
        <table width="90%" align="center" border="1">
            <xsl:apply-templates select="authors/author[position() mod $cols = 1 or position() = 1]" mode="row"/>
        </table>
    </xsl:template>

    <xsl:template match="authors/author" mode="row">
        <tr>
            <xsl:apply-templates select=". | following-sibling::author[position() &lt; $cols]" mode="cell"/>
        </tr>
    </xsl:template>

    <xsl:template match="authors/author" mode="cell">
        <td>
            <xsl:value-of select="name"/>
        </td>
    </xsl:template>

</xsl:stylesheet>

If you decide to distribute the authors in a different amount of columns, you can either pass a cols parameter with a different value or replace the value in the parameter <xsl:param name="cols">3</xsl:param> with another value. For example, if you change it to 4 columns, it will distribute your authors like this:

<table width="90%" align="center" border="1">
   <tr>
      <td>Marina Machado</td>
      <td>Nuno Vieira</td>
      <td>Damien Vaz</td>
      <td>Tiago Machado</td>
   </tr>
   <tr>
      <td>Manuel Vieira</td>
      <td>Damien Vaz</td>
   </tr>
</table>

The number of rows is calculated in this template:

<xsl:template match="frontmatter" >
    <table width="90%" align="center" border="1">
        <xsl:apply-templates select="authors/author[position() mod $cols = 1 or position() = 1]" mode="row"/>
    </table>
</xsl:template>

The XPath expression selects the authors whose position divided by the number of columns has a remainder of one. For three columns, it will select <author> elements number 1, 4, 7, 10, ... It will also select the last element (which might not be one of those). It will call the first author template tagged with mode="row".

That template selects the elements that will contain the data for the cells of each row:

<xsl:template match="authors/author" mode="row">
    <tr>
        <xsl:apply-templates select=". | following-sibling::author[position() &lt; $cols]" mode="cell"/>
    </tr>
</xsl:template>

It will select the current author (.), and the following siblings at a distance of $cols - 1. If the $cols is 3, then it will select the current author and the next two authors if they exist. That way, it will select all elements that will be placed in a row. For example, if the previous template selected elements 1 and 4 (for 3 columns), this template will select elements 1 (current), 2 and 3 for the first column, and elements 4 (current), 5 and 6 for the second. This selection will be used to apply the mode="cell" template which simply prints the name child element of the author inside a <td> block.

This solution works with XSLT 1.0 or 2.0.

You can see the code working in this XSLT Fiddle



回答2:

You can use xsl:for-each-group and group the author elements using group-ending-with where the position() mod 3 of the matched author elements is equal to 0.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">

    <xsl:output indent="yes"></xsl:output>

    <xsl:template match="/report">
        <html>
            <head>
                <meta charset="utf-8"/>
                <title><xsl:value-of select="frontmatter/title"/></title>
            </head>
            <body>
                <xsl:apply-templates select="frontmatter"/>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="frontmatter" >
        <table width="100%">
            <tr align="center">
                <td>
                    <h1><xsl:value-of select="title"/></h1>
                </td>
            </tr>
            <xsl:if test="subtitle != '' ">
                <tr align="center">
                    <td>
                        <xsl:value-of select="subtitle"/>
                    </td>
                </tr>
            </xsl:if>
        </table>
        <hr width="90%"/>
        <h2>Autores</h2>
        <table width="90%" align="center">
            <xsl:apply-templates select="authors"/>
        </table>
    </xsl:template>

    <xsl:template match="authors">
        <xsl:param name="cols" select="3" as="xs:integer"/>
        <xsl:for-each-group select="author" 
                          group-ending-with="author[position() mod $cols = 0]">
         <tr>
             <xsl:for-each select="current-group()">
                 <td>
                     <p><xsl:value-of select="name"></xsl:value-of></p>
                 </td>
             </xsl:for-each>
             <!--ensure that there are an equal number of columns for every row
                 by generating empty cols if there is a "remainder" -->
             <xsl:for-each select="0 to ($cols - count(current-group())-1)">
                 <td></td>
             </xsl:for-each>
         </tr>   
        </xsl:for-each-group>
    </xsl:template>

</xsl:stylesheet>


标签: xml xslt