Search code examples
xmlxsltxslt-1.0xslt-grouping

Grouping and filling missing table elements in xslt


I have an xml like the one given below. I am trying to convert this xml to a html table. The "ServerName" node becomes column header. Rows are grouped under "Category" Name. When a "Category->Name" or "Parameter->key" is not present for any of the "Data" node, it should display "NA".

I tried my best to solve the problem myself, but I fail miserably in grouping and filling the missing elements. Any help is highly appreciated.

<Results>
  <Data>
    <ServerName>Server1</ServerName>
    <CategorizedData>
      <Category>
        <Parameters>
          <Parameter key="A" value="1" />
          <Parameter key="B" value="2" />
          <Parameter key="C" value="3" />
        </Parameters>
      </Category>
      <Category Name="Misc">
        <Parameters>
          <Parameter key="A" value="2" />
          <Parameter key="B" value="5" />
        </Parameters>
      </Category>
    </CategorizedData>
  </Data>
  <Data>
    <ServerName>Server2</ServerName>
    <CategorizedData>
      <Category>
        <Parameters>
          <Parameter key="A" value="10" />
          <Parameter key="B" value="20" />
          <Parameter key="D" value="30" />
        </Parameters>
      </Category>
      <Category Name="Misc">
        <Parameters>
          <Parameter key="C" value="2" />
          <Parameter key="D" value="5" />
        </Parameters>
      </Category>
    </CategorizedData>
  </Data>
</Results>

<table>
	<tr>
		<td></td>
		<td>Server1</td>
		<td>Server2</td>
	</tr>
	<tr>
		<td>A</td>
		<td>1</td>
		<td>10</td>
	</tr>
	<tr>
		<td>B</td>
		<td>2</td>
		<td>20</td>
	</tr>
	<tr>
		<td>C</td>
		<td>3</td>
		<td>NA</td>
	</tr>
	<tr>
		<td>D</td>
		<td>NA</td>
		<td>30</td>
	</tr>
	<tr>
		<td>Misc</td>
		<td></td>
		<td></td>
	</tr>
	<tr>
		<td>A</td>
		<td>2</td>
		<td>NA</td>
	</tr>
	<tr>
		<td>B</td>
		<td>5</td>
		<td>NA</td>
	</tr>
	<tr>
		<td>C</td>
		<td>NA</td>
		<td>2</td>
	</tr>
	<tr>
		<td>D</td>
		<td>NA</td>
		<td>5</td>
	</tr>
</table>


Solution

  • Using XSLT 1: Example: https://xsltfiddle.liberty-development.net/bFukv8n

    <?xml version="1.0" encoding="UTF-8"?>
    

    <xsl:template match="/">
        <xsl:variable name="vMsg" select="."/>
        <xsl:variable name="vServerNames" select="/Results/Data/ServerName" />
        <table>
            <tr>
                <td/>
                <xsl:for-each select="$vServerNames">
                    <td>
                        <xsl:value-of select="."/>
                    </td>
                </xsl:for-each>
            </tr>
            <!-- Categories with no Name -->
            <xsl:for-each select="$vMsg//Category[not(@Name)]/Parameters/Parameter[not(@key = following::Category[not(@Name)]/Parameters/Parameter/@key)]">
                <xsl:variable name="vCurrKey" select="@key" />
                <tr>
                    <td>
                        <xsl:value-of select="$vCurrKey"/>
                    </td>
                    <xsl:for-each select="$vServerNames">
                        <xsl:variable name="vCurrServer" select="text()" />
                        <xsl:variable name="vCurrVal" select="$vMsg/Results/Data[ServerName = $vCurrServer]/CategorizedData/Category[not(@Name)]/Parameters/Parameter[@key = $vCurrKey]"/>
                        <xsl:choose>
                            <xsl:when test="boolean($vCurrVal)">
                                <td>
                                    <xsl:value-of select="$vCurrVal/@value"/>
                                </td>
                            </xsl:when>
                            <xsl:otherwise>
                                <td>NA</td>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:for-each>
                </tr>
            </xsl:for-each>
            <!-- Categories with Name -->
            <xsl:for-each select="//Category[@Name and not(@Name = following::Category/@Name)]">
                <xsl:variable name="vCatName" select="@Name"/>
                <tr>
                    <td>
                        <xsl:value-of select="$vCatName"/>
                    </td>
                    <td/>
                    <td/>
                </tr>
                <xsl:variable name="vKeys" select="$vMsg//Category[@Name = $vCatName]/Parameters/Parameter[not(@key = following::Category[@Name = $vCatName]/Parameters/Parameter/@key)]" />
                <xsl:for-each select="$vKeys">
                    <xsl:variable name="vCurrKey" select="@key" />
                    <tr>
                        <td>
                            <xsl:value-of select="$vCurrKey"/>
                        </td>
                        <xsl:for-each select="$vServerNames">
                            <xsl:variable name="vCurrServer" select="text()" />
                            <xsl:variable name="vCurrVal" select="$vMsg/Results/Data[ServerName = $vCurrServer]/CategorizedData/Category[@Name = $vCatName]/Parameters/Parameter[@key = $vCurrKey]"/>
                            <xsl:choose>
                                <xsl:when test="boolean($vCurrVal)">
                                    <td>
                                        <xsl:value-of select="$vCurrVal/@value"/>
                                    </td>
                                </xsl:when>
                                <xsl:otherwise>
                                    <td>NA</td>
                                </xsl:otherwise>
                            </xsl:choose>
                        </xsl:for-each>
                    </tr>
                </xsl:for-each>
            </xsl:for-each>
        </table>
    </xsl:template>