xmlxslthtml-tablesparse-array

Convert Sparsely-Populated XML List to HTML Table Using XSL


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.


Solution

  • 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>