【问题标题】:Using XSL to obtain attribute value elsewhere in XML document使用 XSL 获取 XML 文档中其他地方的属性值
【发布时间】:2011-05-07 08:41:42
【问题描述】:

我运行 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 魔法才能实现我的目标?

【问题讨论】:

  • 你为什么使用那些local-name() 而不是直接的节点名称?假设没有 XML 命名空间(至少在您发布的 XML 中没有),/cruisecontrol/build/TestRun/Results/UnitTestResult 将等同于 /cruisecontrol/build/*[local-name()='TestRun']/*[local-name()='Results']/*[local-name()='UnitTestResult']
  • 我不太确定;我在这方面是一个相当大的菜鸟,并且正在接受别人以前的工作。感谢您的帮助和评论;将看看改变它。我绝对愿意接受有关清理和采用更好做法的进一步建议。

标签: xslt xpath cruisecontrol.net stylesheet


【解决方案1】:

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

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

具体来说,[@id=@testId] 部分。您正在尝试根据UnitTestResulttestid 属性中的id 属性查找UnitTest。问题是在这个上下文中[@id=@testId] 的意思是“查找 id 和 testId 属性匹配的 UnitTest 元素。”

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

[@id=current()/@testId]

此外,就像 Lucero 评论的那样,您需要删除您的 local-name() 调用,因为它会让一切变得更简单:

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

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

像这样定义一个键:

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

然后像这样使用它:

<xsl:apply-templates select="key('tests',@testId)"/>

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


使用local-name() 函数可能表明您遇到了命名空间问题。如果buildTestRun 元素位于不同的命名空间中,则不能简单地使用/cruisecontrol/build/TestRun/ 来查询它们。这将仅从默认命名空间查询元素。如果TestRun 在另一个命名空间中,则需要在 XSLT 文件中定义该命名空间并在表达式中使用其前缀,如下所示:/cruisecontrol/build/ns:TestRun 其中ns 是相关的命名空间前缀。 local-name() 所做的是忽略元素名称的命名空间部分,绕过正确定义命名空间。当您不知道将在源文档中使用什么命名空间时,它也很有用。

【讨论】:

  • 有趣; 节点是 节点的子节点。 build 节点没有指定命名空间,但 TestRun 节点指定了: xmlns="microsoft.com/schemas/VisualStudio/TeamTest/2006" 这是否意味着它们在不同的命名空间中?
  • @Dark Lord:是的。 xmlns="foo" 是一个默认命名空间声明,这意味着这个和所有没有命名空间前缀的后代元素都在“foo”命名空间中。因此,&lt;TestRun&gt; 在 TeamTest 命名空间中,但 &lt;build&gt; 不在。
  • 太棒了,感谢 Welbog 和 LarsH,我让它按预期工作了。通过在样式表顶部定义 TeamTest 命名空间,我克服了命名空间问题。除此之外,我不得不为该命名空间下的节点使用命名空间以及 local-name 函数。看到它工作真是太棒了。再次感谢您的帮助!
  • +1 对于“使用密钥”的答案。 @Dark Lord Kvl:只要有交叉引用,您就应该使用密钥。作为未成年人:xsl:key/@match 期望一个模式表达式;使用相对模式是一个好习惯(即match="UnitTest" 在这种情况下就足够了)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-05
相关资源
最近更新 更多