0

I have a problem when parsing dynamic xml data into a html table. The XML is as follow.

<results>
  <result id="1" desc="Voltage and current">
    <measure desc="VOLT" value="1.0" />
    <measure desc="AMPERE" value="2.0" />
  </result>
  <result id="2" desc="Current-1">
    <measure desc="AMPERE" value="5.0" />
  </result>
</results>

from which I would like a html table like:

ID   DESC                VOLT   AMPERE
1    Voltage and current 1.0    2.0
2    Current-1                  5.0

Notice the empty cell at second voltage column. ID and DESC is taken from result/@id and result/@desc and the rest of the column names should come from measure/@desc

No column name should be duplicate, I managed to code that far, but when I start adding my measures I need to match each measure/@desc to correct column in the table. I tried double nested loops to first match all unique column names, and then loop all measures again to match the column header. But the xslt parser threw a NPE on me! Sorry that I can't show any code as it is on a non-connected computer.

I've browsed so many Q/A here on SO but to no help for my specific problem.

Thanks in advance

Note: I am able to change the XML format in any way to make parsing easier if anyone come up with a neater format.

1
  • Are you using XSLT1.0 or XSLT2.0? Commented Nov 2, 2012 at 15:26

1 Answer 1

1

If you are using XSLT1.0, you can use a technique called 'Muenchian' grouping to get the distinct measure descriptions, which will form the basis of your head row, and also be used to output the values of each row.

Firstly, you define a key to look up measure elements by their @desc attribute

<xsl:key name="measures" match="measure" use="@desc" />

Then, to get the distinct measure descriptions you can iterate over the measure elements that appear first in the group for their given @desc attribute

<xsl:apply-templates 
   select="result/measure[generate-id() = generate-id(key('measures', @desc)[1])]"
   mode="header" />

Then, for your header, you would simply have a template to output the description.

<xsl:template match="measure" mode="header">
   <th>
      <xsl:value-of select="@desc" />
   </th>
</xsl:template>

For each result row, would do a similar thing, and iterate over all distinct measure values, but the only difference is you would have to pass in the current result element as a parameter, for later use.

<xsl:apply-templates 
   select="/results/result/measure[generate-id() = generate-id(key('measures', @desc)[1])]" 
   mode="data">
  <xsl:with-param name="result" select="." />
</xsl:apply-templates>

Then, in the template that matched the measure this time, you could access the measure within the result element with a matching @desc attribute (and id there is no such attribute, nothing is output for the cell)

<xsl:template match="measure" mode="data">
   <xsl:param name="result" />
   <td>
      <xsl:value-of select="$result/measure[@desc = current()/@desc]/@value" />
   </td>
</xsl:template>

Here is the full XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" indent="yes"/>
   <xsl:key name="measures" match="measure" use="@desc" />

   <xsl:template match="/results">
      <table>
         <tr>
            <th>ID</th>
            <th>DESC</th>
            <xsl:apply-templates select="result/measure[generate-id() = generate-id(key('measures', @desc)[1])]" mode="header" />
         </tr>
         <xsl:apply-templates select="result" />
      </table>
   </xsl:template>

   <xsl:template match="result">
      <tr>
         <td><xsl:value-of select="@id" /></td>
         <td><xsl:value-of select="@desc" /></td>
         <xsl:apply-templates select="/results/result/measure[generate-id() = generate-id(key('measures', @desc)[1])]" mode="data">
            <xsl:with-param name="result" select="." />
         </xsl:apply-templates>
      </tr>
   </xsl:template>

   <xsl:template match="measure" mode="header">
      <th>
         <xsl:value-of select="@desc" />
      </th>
   </xsl:template>

   <xsl:template match="measure" mode="data">
      <xsl:param name="result" />
      <td>
         <xsl:value-of select="$result/measure[@desc = current()/@desc]/@value" />
      </td>
   </xsl:template>
</xsl:stylesheet>

Note the use of the mode attributes because you have two templates matching the measure element, which function in different ways.

When applied to your input XML, the following is output

<table>
   <tr>
      <th>ID</th>
      <th>DESC</th>
      <th>VOLT</th>
      <th>AMPERE</th>
   </tr>
   <tr>
      <td>1</td>
      <td>Voltage and current</td>
      <td>1.0</td>
      <td>2.0</td>
   </tr>
   <tr>
      <td>2</td>
      <td>Current-1</td>
      <td/>
      <td>5.0</td>
   </tr>
</table>
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.