1

I'm trying to convert an XML file into a nice-looking HTML table using XSL, and I can't for the life of me figure out how to get the columns all lined up properly. I found a similar question (Convert XML List to HTML Table Using XSL), which was informative (I'm pretty new to XSL in general so every little bit helps), but my XML is structured a bit more... irregularly:

<?xml version="1.0"?>
<root>
    <Group>
        <Type>Type1</Type>
        <Desc>Desc1</Desc>
        <Name>Name1</Name>
    </Group>
    <Group>
        <Type>Type2</Type>
        <Name>Name2</Name>
        <State>State2</State>
    </Group>
    <Group>
        <Type>Type3</Type>
        <State>State3</State>
        <Country>Country4</Country>
    </Group>
</root>

Is there a good way to turn this into a well-formatted table? I'd prefer if it worked on the XML as it is like this, as if we ever add a new field to one of the Group elements, I don't want them to have to add the same tag to every other element (though if there's alternately a good way to automatically add all the missing children to a Group element that are present in other Group elements, that would be good too, maybe using a second XSLT that can run first). There's no backing schema for this XML, but one could be made if it helps.

Sorry if this has been answered before, I couldn't find anything closer besides the mentioned question and nothing about how to add the missing children to an element.

Community
  • 1
  • 1
Nico
  • 13
  • 5

2 Answers2

1

First, this is not simple. If you're new to XSLT, then this may not the best project for you to undertake.

In order to understand the following solution, you need to be familiar with using keys, as well as the Muenchian grouping technique.

XSLT 1.0

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:key name="col-by-name" match="/root/Group/*" use="name()" />
<xsl:key name="cell-by-rowXcol" match="/root/Group/*" use="concat(generate-id(..), '|', name())" />

<xsl:template match="/root">
    <xsl:variable name="distinct-columns" select="Group/*[count(. | key('col-by-name', name())[1]) = 1]"/>
    <table border="1">
        <thead>
            <tr>
                <xsl:for-each select="$distinct-columns">
                    <th><xsl:value-of select="name()"/></th>
                </xsl:for-each>
            </tr>
        </thead>
        <tbody>
            <xsl:for-each select="Group">
            <xsl:variable name="row" select="generate-id()" />
                <tr>
                    <xsl:for-each select="$distinct-columns">
                        <td>
                            <xsl:value-of select="key('cell-by-rowXcol', concat($row, '|', name()))" />
                        </td>
                    </xsl:for-each>
                </tr>
            </xsl:for-each>
        </tbody>
    </table>
</xsl:template>

</xsl:stylesheet>

Result (rendered):

enter image description here

In XSLT 2.0, you can use the distinct-values() function instead of Muenchian grouping.

michael.hor257k
  • 96,733
  • 5
  • 30
  • 46
0

Here is my attempt at an XSLT 2.0 solution:

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="html" version="5.0" encoding="UTF-8" indent="yes" />

    <xsl:variable name="col-names" select="distinct-values(root/Group/*/node-name(.))"/>

    <xsl:template match="root">
        <table>
            <thead>
                <tr>
                    <xsl:for-each select="$col-names">
                        <th>
                            <xsl:value-of select="."/>
                        </th>
                    </xsl:for-each>
                </tr>
            </thead>
            <tbody>
                <xsl:apply-templates select="Group"/>                
            </tbody>
        </table>
    </xsl:template>

    <xsl:template match="Group">
        <tr>
            <xsl:variable name="this" select="."/>
            <xsl:for-each select="$col-names">
                <td>
                    <xsl:value-of select="$this/*[node-name(.) eq current()]"/>
                </td>
            </xsl:for-each>
        </tr>
    </xsl:template>
</xsl:transform>
Martin Honnen
  • 138,662
  • 6
  • 76
  • 93
  • This one worked like a charm as soon as I found a processor that liked it (XML Spy does, the one built into our WAS runtime we use in Eclipse doesn't). That distinct-values() function was the big thing I was missing, that's exactly what I needed. Thank you and Michael for your help. – Nico Aug 21 '15 at 20:05