xmlxsltxsl-stylesheet

XSLT for dynamic nested XML tables


I am new to xslt and facing a problem as I have generated an xml file dynamically (i.e. node names are unknown) And tried many approaches to view the content in nested tables in html file using xslt, but all my efforts are failing.

<?xml version="1.0" encoding="UTF-8"?>
<content>
    <Source>
        <Column1>Name</Column1>
        <Column2>Organization Name</Column2>
        <SubSource>
            <Column1>Name</Column1>
            <Column2>Person Name</Column2>
            <Column1>Address</Column1>
            <Column2>House: E5, Block-G, Road-02</Column2>
            <Details>
                <Column1>Entry date</Column1>
                <Column2>6/6/13 12:04 PM</Column2>
                <Column1>height</Column1>
                <Column2>153</Column2>
                <Column1>weight</Column1>
                <Column2>53.5</Column2>
                <Column1>temperature</Column1>
                <Column2>98.67</Column2>
                <Column1>pulse rate</Column1>
                <Column2>76</Column2>
                <Advices>
                    <Advice>
                        <Column1>Title</Column1>
                        <Column2>Workout</Column2>
                        <Column1>Location</Column1>
                        <Column2>In the Park</Column2>
                    </Advice>
                    <Advice>
                        <Column1>Title</Column1>
                        <Column2>Eating Habit</Column2>
                        <Column1>Remarks</Column1>
                        <Column2>Eat Less</Column2>
                        <Column1>Notes</Column1>
                        <Column2>Avoid salts</Column2>
                    </Advice>
                </Advices>
            </Details>
        </SubSource>
    </Source>
    <Source>
        <Column1>Name</Column1>
        <Column2>Organization Name</Column2>
        <SubSource>
            <Column1>Name</Column1>
            <Column2>Person Name</Column2>
            <Column1>Address</Column1>
            <Column2>House: E5, Block-G, Road-02</Column2>
            <Details>
                <Column1>Entry date</Column1>
                <Column2>6/6/13 12:04 PM</Column2>
                <Column1>height</Column1>
                <Column2>153</Column2>
                <Column1>weight</Column1>
                <Column2>53.5</Column2>
                <Column1>temperature</Column1>
                <Column2>98.67</Column2>
                <Column1>pulse rate</Column1>
                <Column2>76</Column2>
                <Advices>
                    <Advice>
                        <Column1>Title</Column1>
                        <Column2>Workout</Column2>
                        <Column1>Location</Column1>
                        <Column2>In the Park</Column2>
                    </Advice>
                </Advices>
            </Details>
        </SubSource>
    </Source>
</content>

I have used few xslt approaches. However all of them not viewing the data as wanted, either less or not in properly format table!!!

The following was my best approach (I didn't do the multiple tables design as I wanted to get all data first)

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html"/>
    <xsl:template match="/">
        <html>
            <body>
                <table>
                    <xsl:apply-templates select="content/Source"/>
                </table>
            </body>
        </html>
    </xsl:template>
    <xsl:template match="content/Source">
        <xsl:variable name="column" select="content/Source"/>
        <tr>
            <td>
                <xsl:value-of select="Column1"/>
            </td>
            <td>
                <xsl:value-of select="Column2"/>
            </td>
        </tr>
        <xsl:apply-templates select="SubSource"/>
    </xsl:template>
    <xsl:template match="SubSource">
        <tr>
            <td>
                <xsl:value-of select="Column1"/>
            </td>
            <td>
                <xsl:value-of select="Column2"/>
            </td>
        </tr>
        <xsl:apply-templates select="Details"/>
    </xsl:template>
    <xsl:template match="Details">
        <tr>
            <td>
                <xsl:value-of select="Column1"/>
            </td>
            <td>
                <xsl:value-of select="Column2"/>
            </td>
        </tr>
        <xsl:apply-templates select="Advices"/>
    </xsl:template>
    <xsl:template match="Advices">
        <xsl:for-each select="Advice">
            <tr>
                <td>
                    <xsl:value-of select="Column1"/>
                </td>
                <td>
                    <xsl:value-of select="Column2"/>
                </td>
            </tr>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

And the output is:

 <table>
         <tr>
            <td>Name</td>
            <td>Organization Name</td>
         </tr>
         <tr>
            <td>Name</td>
            <td>Person Name</td>
         </tr>
         <tr>
            <td>Entry date</td>
            <td>6/6/13 12:04 PM</td>
         </tr>
         <tr>
            <td>Title</td>
            <td>Workout</td>
         </tr>
         <tr>
            <td>Title</td>
            <td>Eating Habit</td>
         </tr>
         <tr>
            <td>Name</td>
            <td>Organization Name</td>
         </tr>
         <tr>
            <td>Name</td>
            <td>Person Name</td>
         </tr>
         <tr>
            <td>Entry date</td>
            <td>6/6/13 12:04 PM</td>
         </tr>
         <tr>
            <td>Title</td>
            <td>Workout</td>
         </tr>
      </table>

My target is to get get table layout same as the XML structured.

Advices for better XML format are always welcomed!

Thank you


Solution

  • This XSLT 1.0 stylesheet does what you seem to want: It creates nested HTML tables from a structure of unknown element names.

    I the only prerequisite is that there are <Column1> elements that are possibly followed by an associated <Column2>.

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
      <xsl:output method="html" indent="yes" />
    
      <!-- #1: anything that has <Column1> children becomes a table -->
      <xsl:template match="*[Column1]">
        <table class="{name()}">
          <xsl:apply-templates select="*" mode="nested" />
        </table>
      </xsl:template>
    
      <!-- #2: ...if it's nested, it is wrapped in a table row first -->
      <xsl:template match="*[Column1]" mode="nested">
        <tr>
          <td colspan="2">
            <!-- this actually executes #1 because it uses no mode! -->
            <xsl:apply-templates select="." />
          </td>
        </tr>
      </xsl:template>
    
      <!-- #3: <Column1> governs the creation of table rows -->
      <xsl:template match="*[Column1]/Column1" mode="nested">
        <tr>
          <td><xsl:value-of select="." /></td>
          <td><xsl:value-of select="following-sibling::*[1][self::Column2]" /></td>
        </tr>
      </xsl:template>
    
      <!-- #4: suppress any other data field (like <Column2>) -->
      <xsl:template match="*[Column1]/*[not(*)]" mode="nested" />
    
    </xsl:stylesheet>
    

    Note the use of template modes: If there is more than one template that can match a certain element, you can use mode to differentiate between them.

    In this case, you want to create nested tables. At the root level (the default case), you can simply create a <table> element.

    Hovever, if you already are inside a <table>, you must create a <tr>/<td> first, but after that the process is the same.

    By using modes I can make sure that the right template is executed in the right context without having to repeat myself when it comes to creating the actual <table>.

    As a side effect, by using modes we could create a whole set of templates that can be dropped into an existing stylesheet without affecting the established workflow.


    following-sibling::*[1][self::Column2] is "The first following sibling, but only if it actually is a <Column2>.".


    The template #4 is necessary since the rather unspecific <xsl:apply-templates select="*" mode="nested" /> also applies to <Column2>. However, those are actually already handled by #3, which means unless we explicitly suppress them they would generate unwanted duplicate output.