从另一个节点的属性值中指定的路径检索XML节点

时间:2010-06-10 15:38:24

标签: xml xslt xpath

从这个XML来源:

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

我想生成这个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>

这是我目前拥有的XSLT:

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

它几乎可以工作,我遇到的问题是从STRUCT块的“nodeName”属性值中指定的路径中检索正确的DATA节点。

3 个答案:

答案 0 :(得分:7)

这是一个不使用任何扩展程序的纯XSLT 1.0解决方案

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

将此转换应用于提供的XML文档

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

产生了想要的正确结果

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

答案 1 :(得分:2)

你得到的问题:

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

是假设elementName只是单个元素的名称。如果它是一个任意的XPath表达式,测试将失败。

您将遇到(或可能已经遇到)的第二个问题是select子句中不允许使用属性值模板,因此您不能执行以下简单的操作:

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

您需要的是能够为您正在寻找的元素动态创建XPath表达式,然后动态评估该表达式。

对于解决方案,我转向evaluate()库中的EXSLT的dynamic函数。我不得不使用它两次:一次构建表示查询的整个XPath表达式,一次用于评估该查询。这种方法的优点是可以访问evaluate的完整XPath解析和执行功能。

<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)"/>

其中dyn命名空间被声明为http://exslt.org/dynamic。找出这里引用的地方很棘手,并且我试了好几次。

使用这些而不是你的elementName和value-of表达式,我得到:

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

这是我认为你正在寻找的。

不幸的是,我不熟悉MSXML,所以我不能告诉你特定的XSLT处理器是否支持这种扩展或类似的东西。

答案 2 :(得分:0)

欧文,

使用dyn:evaluate>()的解决方案很好,但在浏览器中不起作用,请参见此处:

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

你得到的问题: ... 是假设elementName只是一个元素的名称。 如果它是一个任意的XPath表达式,测试将失败。

Dimitrie的解决方案不适用于一般的XPath解析和处理 通过添加可以简单地将多个节点添加到他的解决方案中 <xsl:for-each ...> s,请参见下面的差异:

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

$