Home > Software engineering >  Generic XSLT script to convert xml to HTML table (accomodating for potentially missing elements)
Generic XSLT script to convert xml to HTML table (accomodating for potentially missing elements)

Time:08-15

I have an XSLT script that is producing an HTML table that is subsequently emailed (hence the seemingly complex zebra striping code due to rendering in Outlook). An example XML source is as follows:

<Data>
    <Features>
        <Product caption="Product" />
        <ItemName caption="Item Name" />
        <Category caption="Category" />
        <Material caption="Material" />
        <HeightCM caption="Height"  />
        <AssignedTo caption="Category Manager" />
    </Features>
    <Product>
        <Value>001</Value>
        <ItemName>Product 1</ItemName>
        <Category>Electic<Category>
        <Material>Steel<Material>
        <HeightCM>15</HeightCM>
        <AssignedTo>James</AssignedTo>
    </Product>  
    <Product>
        <Value>002</Value>
        <ItemName>Product 2</ItemName>
        <HeightCM>12</HeightCM>
    </Product>
    <Product>
        <Value>003</Value>
        <ItemName>Product 3</ItemName>
        <Category>Sale<Category>
        <AssignedTo>Jane</AssignedTo>       
    </Product>
</Data>

So there is a Features element that defines the header row, and Product elements that contain the values for the Product attributes, although these are not always supplied for all Features children, as per the example (Product 1 has all features, but 2 and 3 are missing some). My script (actually adapted from a stack overflow response from another users' question) works fine when all Products are supplied with data for all the Features, however the script only generates table cells for supplied data, causing the resulting HTML (although faithfully rendered) to appear to have misaligned columns.

How can I adapt the script to remain as generic as possible (only Data, Features and Product are guaranteed) but also to accomodate for the empty table cells required when the Feature is missing from the Product?

Here is the XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="html" indent="yes"/>
    <xsl:template match="/*">
        <html>
            <head>
                <style>
                [Removed for legibility reasons]
                </style>
            </head>
            <body>
                <table>
                    <thead>
                        <tr>
                            <xsl:apply-templates select="Features/*" mode="th"/>
                        </tr>
                    </thead>
                    <tbody>
                        <xsl:apply-templates select="*"/>
                    </tbody>
                </table>
            </body>
        </html>
    </xsl:template>
    <xsl:template match="/*/*/*" mode="th">
        <th>
            <xsl:value-of select="@caption"/>
        </th>
    </xsl:template>
    <xsl:template match="/*/*">
        <tr>
            <xsl:attribute name="class">
                <xsl:choose>
                    <xsl:when test="position() mod 2 = 0">even</xsl:when>
                    <xsl:when test="position() mod 2 = 1">odd</xsl:when>
                </xsl:choose>
            </xsl:attribute>
            <xsl:apply-templates select="*"/>
        </tr>
    </xsl:template>
    <xsl:template match="/*/*/*">
        <td>
            <xsl:value-of select="."/>
        </td>
    </xsl:template>
</xsl:stylesheet>

CodePudding user response:

Try something like:

XSLT 1.0

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

<xsl:template match="/Data">
    <xsl:variable name="cols" select="Features/*" />
    <html>
        <head>
            <style>
                <!-- [Removed for legibility reasons] -->
            </style>
        </head>
        <body>
            <table>
                <thead>
                    <tr>
                        <xsl:for-each select="$cols">
                            <th>
                                <xsl:value-of select="@caption"/>
                            </th>
                        </xsl:for-each>
                    </tr>
                </thead>
                <tbody>
                    <xsl:for-each select="Product">
                        <xsl:variable name="cells" select="*" />
                        <tr>
                            <!-- add class here -->
                            <xsl:for-each select="$cols">
                                <td>
                                    <xsl:value-of select="$cells[name()=name(current())]"/>
                                </td>
                            </xsl:for-each>
                        </tr>
                    </xsl:for-each>
                    
                </tbody>
            </table>
        </body>
    </html>
</xsl:template>

</xsl:stylesheet>
  • Related