使用XSL在XML文档中的其他位置获取属性值

时间:2010-11-09 16:19:56

标签: xslt xpath cruisecontrol.net stylesheet

我运行CruiseControl.NET并尝试构建一个XSL样式表,从XML构建日志中提取有关已损坏单元测试的某些信息。对于每个损坏的单元测试,我想在XSL中获取单元测试的名称,单元测试所在的类名以及失败消息。目前,我可以获取单元测试名称和失败消息,但是我无法获得类名。我认为这是因为单元测试的类名只包含在XML构建日志的不同区域,而我是XSL的菜鸟。这是我的XSL样式表的简化示例:

<!-- Unit tests -->
<xsl:template match="/">
 <table border="1" width="100%">
  <tr>
   <th align="left">Class</th>
   <th align="left">Method</th>
   <th align="left">Message</th>
  </tr>
  <xsl:apply-templates select="/cruisecontrol/build/*[local-name()='TestRun']/*[local-name()='Results']/*[local-name()='UnitTestResult']"/>
 </table>
</xsl:template>

<!-- Failed uint test -->
<xsl:template match="*[local-name()='UnitTestResult'][@outcome='Failed']">
 <tr>
  <td>
   <xsl:apply-templates select="/cruisecontrol/build/*[local-name()='TestRun']/*[local-name()='TestDefinitions']/*[local-name()='UnitTest'][@id=@testId]"/>
  </td>
  <td>
   <xsl:value-of select="@testName"/>
  </td>
  <td>
   <xsl:value-of select="*[local-name()='Output']/*[local-name()='ErrorInfo']/*[local-name()='Message']"/>
  </td>
 </tr>
</xsl:template>

<!-- Failed unit test class name -->
<xsl:template match="/cruisecontrol/build/*[local-name()='TestRun']/*[local-name()='TestDefinitions']/*[local-name()='UnitTest']">
 <xsl:value-of select="@className"/>
</xsl:template>

以下是一个简化的示例XML构建日志:

<cruisecontrol>
 <build>
  <TestRun>
   <TestDefinitions>
    <UnitTest name="MyUnitTest" storage="Test.dll" id="b17e5b2a-47d0-5f78-1750-07c8ac14518c">
     <TestMethod className="UnitTests.cs" name="MyUnitTest" />
    </UnitTest>
    ...
   </TestDefinitions>
   <Results>
    <UnitTestResult testId="b17e5b2a-47d0-5f78-1750-07c8ac14518c" testName="MyUnitTest" outcome="Failed">
     <Output>
      <ErrorInfo>
       <Message>Assert.AreEqual failed</Message>
      </ErrorInfo>
     </Output>
    </UnitTestResult>
    ...
   </Results>
  </TestRun>
 </build>
</cruisecontrol>

这是当前的输出:

  

单元测试类名称:

     

单元测试方法名称:MyUnitTest

     

失败消息:Assert.AreEqual失败

这是所需的输出:

  

单元测试类名称:UnitTests.cs

     

单元测试方法名称:MyUnitTest

     

失败消息:Assert.AreEqual失败

我的问题是类名始终为空。我试图使用UnitTestResult节点的testId属性来引用文档中其他位置的正确UnitTest节点。我需要什么XSL或Xpath魔法来实现我的目标?

1 个答案:

答案 0 :(得分:2)

乍一看,这似乎是你的问题:

<xsl:apply-templates select="/cruisecontrol/build/*[local-name()='TestRun']/*[local-name()='TestDefinitions']/*[local-name()='UnitTest'][@id=@testId]"/>

具体来说,[@id=@testId]部分。您正在尝试根据UnitTest的{​​{1}}属性中的id属性查找testid。问题出在这种情况下UnitTestResult意味着“查找id和testId属性匹配的UnitTest元素。”

你真正想要的是使用current()函数,它允许你在过滤器中有多个上下文,如下所示:

[@id=@testId]

此外,就像Lucero所评论的那样,您需要删除[@id=current()/@testId] 来电,因为它会让一切变得更简单:

local-name()

作为对查找使用长XPath表达式的替代方法,您可以使用keys代替。它们允许您定义一个键,以后可以用于更短的查找。

定义一个这样的键:

<xsl:apply-templates 
  select="/cruisecontrol/build/TestRun/TestDefinitions/UnitTest[@id=current()/@testId]"
/>

然后像这样使用它:

<xsl:key 
  name="tests" 
  match="/cruisecontrol/build/TestRun/TestDefinitions/UnitTest" 
  use="@id"/>

这使您不必使用current()函数。无论哪种方式,它都会起作用。


使用<xsl:apply-templates select="key('tests',@testId)"/> 函数可能表示您遇到了命名空间问题。如果local-name()build元素位于不同的命名空间中,则无法使用TestRun简单地查询它们。这将仅查询默认命名空间中的元素。如果/cruisecontrol/build/TestRun/在另一个名称空间中,则需要在XSLT文件中定义该名称空间并在表达式中使用其前缀,如下所示:TestRun其中/cruisecontrol/build/ns:TestRun是名称空间前缀题。 ns做的是忽略元素名称的命名空间部分,绕过正确定义命名空间。当您不知道将在源文档中使用哪些名称空间时,它也很有用。