Aki K
Aki K

Reputation: 1262

Outputting multiple tables in XSLT at once

I have an XML document:

<Gym>

<Trainee>
    <Trainee_ID>1521</Trainee_ID>
    <Trainee_Name>Mary Andersen</Trainee_Name>
    <Trainee_Age>23</Trainee_Age>
</Trainee>

<Trainee>
    <Trainee_ID>1522</Trainee_ID>
    <Trainee_Name>Jane Sellers</Trainee_Name>
    <Trainee_Age>56</Trainee_Age>
</Trainee>

<Trainee>
    <Trainee_ID>1523</Trainee_ID>
    <Trainee_Name>Julie Aniston</Trainee_Name>
    <Trainee_Age>32</Trainee_Age>
</Trainee>

<Class>
    <Trainee_ID>1521</Trainee_ID>
    <Course_ID>A21</Course_ID>
    <Class_Room>A1</Class_Room>
</Class>

<Class>
    <Trainee_ID>1522</Trainee_ID>
    <Course_ID>A22</Course_ID>
    <Class_Room>B2</Class_Room>
</Class>

<Class>
    <Trainee_ID>1523</Trainee_ID>
    <Course_ID>B24</Course_ID>
    <Class_Room>B3</Class_Room>
</Class>

<Course>
    <Course_ID>A21</Course_ID>
    <Course_Title>Yoga</Course_Title>
    <Course_Hours_Per_Week>2</Course_Hours_Per_Week>
</Course>

<Course>
    <Course_ID>A22</Course_ID>
    <Course_Title>Pilates</Course_Title>
    <Course_Hours_Per_Week>2</Course_Hours_Per_Week>
</Course>

<Course>
    <Course_ID>B24</Course_ID>
    <Course_Title>Aerobic</Course_Title>
    <Course_Hours_Per_Week>3</Course_Hours_Per_Week>
</Course>

</Gym>

What I want to do is make 3 tables with XSL, with header the name of the children element (only once), sub-headers the name of the grandchildren elements (only once) and the rest rows and columns will be filled with the values of the grandchildren elements.

This is as far as I got:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/Gym">
    <html>
    <body>
        <xsl:for-each select="*">
            <table border="1">
            <tr>
            <xsl:if test="position()='1' or position()='4' or position()='7'">
            <xsl:value-of select="translate(name(.), 'abcdefghijklnmopqrstuvwxyz', 'ABCDEFGHIJKLNMOPQRSTUVWXYZ')"/>     
            </xsl:if>
            </tr>
            <tr>
            <xsl:if test="position()='1' or position()='4' or position()='7'">
                <xsl:for-each select="*">
                <th><xsl:value-of select="name(.)"/></th>
                </xsl:for-each>
            </xsl:if>
            </tr>
            <tr>        
            <xsl:for-each select="*">   
                <th>            
                <xsl:value-of select="."/>
                </th>
            </xsl:for-each>
            </tr>
            </table>
            <p></p>
        </xsl:for-each>
    </body>
    </html>
</xsl:template>
</xsl:stylesheet>

and this is the output:

Output
(source: UploadScreenshot.com)

What is happening is that every time a new table is being created for each sub-element but what I want is a smart code that will make automatically all 3 tables and then make the headers and sub-headers and after that it will input the data. What I want is something like this (ignore the colors and the text formatting):

Output

What I want my XSLT code to do is create one table for each element instance, then put the name of the element as a header, after that put as headers the sub-elements once and then fill the table with the data.

Upvotes: 1

Views: 5140

Answers (1)

JLRishe
JLRishe

Reputation: 101730

The approach to use here is to use Muenchian grouping and keys to find distinct groups and members of them. I believe this should work:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
  <xsl:key name="kCategory" match="/*/*" use="local-name()"/>

  <xsl:template match="/*">
    <div>
      <xsl:apply-templates 
        select="*[generate-id() = generate-id(key('kCategory', local-name())[1])]" />
    </div>
    </xsl:template>

  <xsl:template match="/*/*">
    <table>
      <tr class="table-header odd-row">
        <th colspan="3">
          <xsl:value-of select="local-name()"/>
        </th>
      </tr>
      <tr class="column-headers">
        <xsl:apply-templates select="*" mode="colHeader" />
      </tr>
      <xsl:apply-templates select="key('kCategory', local-name())" mode="row"/>
    </table>
  </xsl:template>

  <xsl:template match="/*/*/*" mode="colHeader">
    <td>
      <xsl:value-of select="local-name()"/>
    </td>
  </xsl:template>

  <xsl:template match="/*/*" mode="row">
    <tr>
      <xsl:if test="position() mod 2 = 1">
        <xsl:attribute name="class">odd-row</xsl:attribute>
      </xsl:if>
      <xsl:apply-templates select="*" mode="value" />
    </tr>
  </xsl:template>

  <xsl:template match="/*/*/*" mode="value">
    <td>
      <xsl:value-of select="." />
    </td>
  </xsl:template>
</xsl:stylesheet>

When run on your sample input, this produces:

<div>
  <table>
    <tr class="table-header odd-row">
      <th colspan="3">Trainee</th>
    </tr>
    <tr class="column-headers">
      <td>Trainee_ID</td>
      <td>Trainee_Name</td>
      <td>Trainee_Age</td>
    </tr>
    <tr class="odd-row">
      <td>1521</td>
      <td>Mary Andersen</td>
      <td>23</td>
    </tr>
    <tr>
      <td>1522</td>
      <td>Jane Sellers</td>
      <td>56</td>
    </tr>
    <tr class="odd-row">
      <td>1523</td>
      <td>Julie Aniston</td>
      <td>32</td>
    </tr>
  </table>
  <table>
    <tr class="table-header odd-row">
      <th colspan="3">Class</th>
    </tr>
    <tr class="column-headers">
      <td>Trainee_ID</td>
      <td>Course_ID</td>
      <td>Class_Room</td>
    </tr>
    <tr class="odd-row">
      <td>1521</td>
      <td>A21</td>
      <td>A1</td>
    </tr>
    <tr>
      <td>1522</td>
      <td>A22</td>
      <td>B2</td>
    </tr>
    <tr class="odd-row">
      <td>1523</td>
      <td>B24</td>
      <td>B3</td>
    </tr>
  </table>
  <table>
    <tr class="table-header odd-row">
      <th colspan="3">Course</th>
    </tr>
    <tr class="column-headers">
      <td>Course_ID</td>
      <td>Course_Title</td>
      <td>Course_Hours_Per_Week</td>
    </tr>
    <tr class="odd-row">
      <td>A21</td>
      <td>Yoga</td>
      <td>2</td>
    </tr>
    <tr>
      <td>A22</td>
      <td>Pilates</td>
      <td>2</td>
    </tr>
    <tr class="odd-row">
      <td>B24</td>
      <td>Aerobic</td>
      <td>3</td>
    </tr>
  </table>
</div>

I presume you can handle the colors and styling? I've added two class attributes where I thought they'd be appropriate.

You should be able to use CSS like the following to get the styles you need (it will surely need some tweaking to get it just right, but this is the general idea:

th, td
{
    border: 2px solid #9BBA58;
    background-color: #E6EDD4;
}

tr.table-header th, tr.column-headers td
{
    font-weight: bold;
}

tr.table-header th
{
    border-bottom: 4px solid #9BBA58;
}

tr.odd-row th, tr.odd-row td
{
    background-color: white;
}

Upvotes: 3

Related Questions