Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrieving XML node from a path specified in an attribute value of another node

Tags:

xml

xslt

xpath

From this XML source :

<?xml version="1.0" encoding="utf-8" ?>
<ROOT>
  <STRUCT>
    <COL order="1" nodeName="FOO/BAR" colName="Foo Bar" />
    <COL order="2" nodeName="FIZZ" colName="Fizz" />
  </STRUCT>

  <DATASET>
    <DATA>
      <FIZZ>testFizz</FIZZ>
      <FOO>
        <BAR>testBar</BAR>
        <LIB>testLib</LIB>
      </FOO>
    </DATA>
    <DATA>
      <FIZZ>testFizz2</FIZZ>
      <FOO>
        <BAR>testBar2</BAR>
        <LIB>testLib2</LIB>
      </FOO>
    </DATA>
  </DATASET>
</ROOT>

I want to generate this HTML :

<html>
  <head>
    <title>Test</title>
  </head>
  <body>
    <table border="1">
      <tr>
        <td>Foo Bar</td>
        <td>Fizz</td>
      </tr>
      <tr>
        <td>testBar</td>
        <td>testFizz</td>
      </tr>
      <tr>
        <td>testBar2</td>
        <td>testFizz2</td>
      </tr>
    </table>
  </body>
</html>

Here is the XSLT I currently have :

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="html" indent="yes"/>

  <xsl:template match="/ROOT">
    <html>
      <head>
        <title>Test</title>
      </head>
      <body>
        <table border="1">
          <tr>
            <!--Generate the table header-->
            <xsl:apply-templates select="STRUCT/COL">
              <xsl:sort data-type="number" select="@order"/>
            </xsl:apply-templates>
          </tr>
          <xsl:apply-templates select="DATASET/DATA" />
        </table>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="COL">
    <!--Template for generating the table header-->
    <td>
      <xsl:value-of select="@colName"/>
    </td>
  </xsl:template>

  <xsl:template match="DATA">
    <xsl:variable name="pos" select="position()" />
    <tr>
      <xsl:for-each select="/ROOT/STRUCT/COL">
        <xsl:sort data-type="number" select="@order"/>
        <xsl:variable name="elementName" select="@nodeName" />
        <td>
          <xsl:value-of select="/ROOT/DATASET/DATA[$pos]/*[name() = $elementName]" />
        </td>
      </xsl:for-each>
    </tr>
  </xsl:template>

</xsl:stylesheet>

It almost works, the problem I have is to retrieve the correct DATA node from the path specified in the "nodeName" attribute value of the STRUCT block.

like image 596
Olivier Payen Avatar asked Jan 22 '23 22:01

Olivier Payen


1 Answers

Here is a pure XSLT 1.0 solution that doesn't use any extensions:

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

 <xsl:template match="/">
  <html>
    <head>
      <title>Test</title>
    </head>
    <body>
      <table border="1">
        <xsl:apply-templates select="*/STRUCT"/>
        <xsl:apply-templates select="*/DATASET/DATA"/>
      </table>
    </body>
  </html>
 </xsl:template>

 <xsl:template match="STRUCT">
  <tr>
    <xsl:apply-templates select="COL"/>
  </tr>
 </xsl:template>

 <xsl:template match="COL">
  <td><xsl:value-of select="@colName"/></td>
 </xsl:template>

 <xsl:template match="DATA">
      <tr>
        <xsl:apply-templates select="/*/STRUCT/*/@nodeName">
         <xsl:with-param name="pCurrentNode" select="."/>
        </xsl:apply-templates>
      </tr>
 </xsl:template>

 <xsl:template match="@nodeName" name="getNodeValue">
   <xsl:param name="pExpression" select="string(.)"/>
   <xsl:param name="pCurrentNode"/>

   <xsl:choose>
    <xsl:when test="not(contains($pExpression, '/'))">
      <td><xsl:value-of select="$pCurrentNode/*[name()=$pExpression]"/></td>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="getNodeValue">
        <xsl:with-param name="pExpression"
          select="substring-after($pExpression, '/')"/>
        <xsl:with-param name="pCurrentNode" select=
        "$pCurrentNode/*[name()=substring-before($pExpression, '/')]"/>
      </xsl:call-template>
    </xsl:otherwise>
   </xsl:choose>

 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<ROOT>
  <STRUCT>
    <COL order="1" nodeName="FOO/BAR" colName="Foo Bar" />
    <COL order="2" nodeName="FIZZ" colName="Fizz" />
  </STRUCT>

  <DATASET>
    <DATA>
      <FIZZ>testFizz</FIZZ>
      <FOO>
        <BAR>testBar</BAR>
        <LIB>testLib</LIB>
      </FOO>
    </DATA>
    <DATA>
      <FIZZ>testFizz2</FIZZ>
      <FOO>
        <BAR>testBar2</BAR>
        <LIB>testLib2</LIB>
      </FOO>
    </DATA>
  </DATASET>
</ROOT>

the wanted, correct result is produced:

<html>
    <head>
        <title>Test</title>
    </head>
    <body>
        <table border="1">
            <tr>
                <td>Foo Bar</td>
                <td>Fizz</td>
            </tr>
            <tr>
                <td>testBar</td>
                <td>testFizz</td>
            </tr>
            <tr>
                <td>testBar2</td>
                <td>testFizz2</td>
            </tr>
        </table>
    </body>
</html>
like image 185
Dimitre Novatchev Avatar answered Jan 26 '23 01:01

Dimitre Novatchev