Extract XML node from the path specified in the attribute value of another node

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 code:

 <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 that 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 is to get the correct DATA node from the path specified in the "nodeName" attribute of the STRUCT block.

+4
source share
3 answers

Here is a clean XSLT 1.0 solution that does not 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 conversion is applied to 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> 

required, the correct result is obtained :

 <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> 
+7
source

The problem with what you have is:

 <xsl:variable name="elementName" select="@nodeName" /> ... <xsl:value-of select="/ROOT/DATASET/DATA[$pos]/*[name() = $elementName]" /> 

is that it assumes elementName is just one element name. If this is an arbitrary XPath expression, the test fails.

The second problem that you will encounter (or perhaps already have) is that attribute value templates are not allowed in select clauses, so you cannot do something simple:

 <xsl:value-of select="/ROOT/DATASET/DATA[$pos]/{$elementName}" /> 

You need something that will dynamically create an XPath expression for the element you are looking for, and then dynamically evaluate that expression.

For a solution, I turned to the EXSLT evaluate() function in the dynamic library. I had to use it twice: once to create the entire XPath expression representing the request, and once to evaluate this request. The advantage of this approach is that you get access to evaluate full XPath parsing and features.

 <xsl:variable name="elementLocation" select="@nodeName" /> <xsl:variable name="query" select="concat('/ROOT/DATASET/DATA[$pos]/', dyn:evaluate('$elementLocation'))"/> ... <xsl:value-of select="dyn:evaluate($query)"/> 

where the dyn namespace is declared at the top as http://exslt.org/dynamic . Finding out where to quote here is difficult, and it took me several attempts to get the right.

Using them instead of the elementName name and expression expression, I get:

 <html xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:dyn="http://exslt.org/dynamic"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <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> 

what i think you are looking for.

Unfortunately, I do not understand MSXML, so I can’t say if your particular XSLT processor supports this extension or something like that.

+2
source

Owen,

your solution using dyn:evaluate>() is fine, but doesn’t work in browsers, see here:

http://www.biglist.com/lists/lists.mulberrytech.com/xsl-list/archives/201008/msg00126.html

The problem with what you have: ... is that it assumes elementName is just one element name. If this is an arbitrary XPath expression, the test will fail.

The Dimitrie solution was not intended for general XPath parsing, and processing more than one node can simply be added to its solution by adding <xsl:for-each ...> s, see below:

  $ diff -u x.xsl y.xsl
 --- x.xsl 2010-08-13 14: 53: 42.000000000 +0200
 +++ y.xsl 2010-08-14 11: 59: 42.000000000 +0200
 @@ -40.15 +40.19 @@

     <xsl: choose>
      <xsl: when test = "not (contains ($ pExpression, '/'))">
 - <td> <xsl: value-of select = "$ pCurrentNode / * [name () = $ pExpression]" /> </td>
 + <xsl: for-each select = "$ pCurrentNode / * [name () = $ pExpression]">
 + <td> <xsl: value-of select = "." /> </td>
 + </ xsl: for-each>
      </ xsl: when>
      <xsl: otherwise>
 + <xsl: for-each select = "$ pCurrentNode / * [name () = substring-before ($ pExpression, '/')]">
        <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: for-each>
      </ xsl: otherwise>
     </ xsl: choose>

 $ 

0
source

Source: https://habr.com/ru/post/1312465/


All Articles