Использование XSL для получения значения атрибута в другом месте документа XML - PullRequest
2 голосов
/ 09 ноября 2010

Я запускаю 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

Моя проблема в том, что имя класса всегда пустое.Я пытаюсь использовать атрибут testId узла UnitTestResult для ссылки на правильный узел UnitTest в другом месте документа.Какая магия XSL или Xpath мне нужна для достижения моей цели?

1 Ответ

2 голосов
/ 09 ноября 2010

На первый взгляд похоже, что это ваша проблема:

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

В частности, [@id=@testId] часть. Вы пытаетесь найти UnitTest на основе его атрибута id из атрибута testid для UnitTestResult. Проблема в этом контексте [@id=@testId] означает «искать элементы UnitTest, чьи атрибуты id и testId совпадают».

Что вам действительно нужно, так это использовать функцию current(), которая позволяет вам иметь более одного контекста в фильтре, например:

[@id=current()/@testId]

Кроме того, как прокомментировал Лусеро, вы захотите удалить свои 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() может указывать на проблему с пространством имен. Если элементы build и TestRun находятся в разных пространствах имен, вы не можете просто запросить их, используя /cruisecontrol/build/TestRun/. Это будет запрашивать элементы только из пространства имен по умолчанию. Если TestRun находится в другом пространстве имен, вам нужно определить это пространство имен в файле XSLT и использовать его префикс в выражениях, например так: /cruisecontrol/build/ns:TestRun, где ns - рассматриваемый префикс пространства имен. local-name() делает то, что он игнорирует часть пространства имен имени элемента, минуя правильное определение пространства имен. Это также полезно, когда вы не знаете, какие пространства имен будут использоваться в исходном документе.

...