如何选择没有特定父级作为祖先的子节点?

时间:2010-06-28 20:33:38

标签: xslt xpath

我们将CruiseControl用于构建服务器。它使用MSBuild编译我们的应用程序,并使用自己的XML记录器,它会像下面的XML一样:

<project name="CI">
  <target name="CompileApp">
    <project name="Project1.csproj">
      <target name="build">
        <error>Compilation error one!</error>
      </target>
      <target name="BeforeBuild">
        <project name="Project2.csproj">
          <target name="build">
            <error>Compilation error two!</error>
          </target>
        </project>
      </target>
    </project>
  </target>
</project>

我想将其转换为输出每个项目错误的报告。我不想报告其他项目中的错误。

  Project "Project1.csproj": 1 error(s)
  Error(s):  
  Compilation error one!

  Project "Project2.csproj": 1 error(s)
  Error(s):
  Compilation error two!

这是我得到的最接近的,但这不对。显示project1的错误时,它不会过滤掉project2的错误。

<xsl:template>
  <xsl:variable select="//project[.//error]" name="projects.with.errors" />
  <xsl:apply-templates select="$projects.with.errors" />
</xsl:template>

<xsl:template match="project">
   <xsl:variable select="./*[not(project)]//error" name="errors" />
   <xsl:if test="count($errors) > 1">
      <!-- display errors -->
   </xsl:if>
</xsl:template>

如何过滤掉与当前项目节点具有不同项目祖先的任何错误节点?即,我怎样才能选择没有项目祖先的后代错误节点?

错误节点可以包含任意数量的父元素(通常但不总是)。

3 个答案:

答案 0 :(得分:2)

此转化

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kProjectByName" match="project"
  use="@name"/>

 <xsl:key name="kErrByProject" match="error"
  use="ancestor::project[1]/@name"/>

 <xsl:template match="/">
   <xsl:for-each select=
    "//project
          [generate-id()
          =
           generate-id(key('kProjectByName', @name)[1])
           ]
    ">
     <xsl:variable name="vErrors" select=
       "key('kErrByProject', @name)"/>

     <xsl:if test="$vErrors">
         Project <xsl:value-of select="@name"/><xsl:text>: </xsl:text>
         <xsl:value-of select="count($vErrors)"/> errors.
         Errors:
         <xsl:for-each select="$vErrors">
           <xsl:value-of select="."/>
         </xsl:for-each>
         <xsl:text>&#xA;</xsl:text>
     </xsl:if>
   </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

应用于提供的XML文档

<project name="CI">
  <target name="CompileApp">
    <project name="Project1.csproj">
      <target name="build">
        <error>Compilation error one!</error>
      </target>
      <target name="BeforeBuild">
        <project name="Project2.csproj">
          <target name="build">
            <error>Compilation error two!</error>
          </target>
        </project>
      </target>
    </project>
  </target>
</project>

生成想要的正确结果

 Project Project1.csproj: 1 errors.
 Errors:
 Compilation error one!

 Project Project2.csproj: 1 errors.
 Errors:
 Compilation error two!

答案 1 :(得分:1)

这是一个非常简单的解决方案:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" />

  <xsl:template match="/">
    <xsl:apply-templates select="//project[.//error]" />
  </xsl:template>

  <xsl:template match="project">
    <xsl:variable name="errors" select=".//error[ancestor::project[1]/@name = current()/@name]" />
    <xsl:if test="count($errors) != 0">
      <xsl:value-of select="concat('Project &quot;',@name,'&quot;: ',count($errors),' error(s)&#10;Error(s):&#10;')" />
      <xsl:apply-templates select="$errors" />
      <xsl:text>&#10;</xsl:text>
    </xsl:if>
  </xsl:template>

  <xsl:template match="error">
    <xsl:value-of select="concat(.,'&#10;')" />
  </xsl:template>
</xsl:stylesheet>

此样式表基本上只调用文档树中任何位置的每个project元素的模板。此模板使用xpath存储变量中所有错误的列表,该xpath选择所有error个元素,这些元素是当前project的后代并且具有当前project元素,因为它是第一个{ {1}}祖先。然后,如果有,它只输出适当的标题文本并将模板应用于每个错误。

我在这里使用project作为新的换行符,但如果你更喜欢windows换行符,可以使用&#10;

对此问题的轻微警告;你最终会在输出的底部添加一个空白行,因为它会在每个项目的底部添加一行以将其与下一个项目分开。

答案 2 :(得分:0)

我们习惯于复杂的要求,我认为当它看起来更简单时我们会感到困惑。

所以,用:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" />

  <xsl:template match="project[../..]">
      <xsl:value-of select="concat('&#xA;',
            'Project &quot;',@name,'&quot;: ',count(target/error),' error(s)&#xA;',
              'Error(s):&#xA;')" />
      <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="error">
    <xsl:value-of select="concat(.,'&#xA;')" />
  </xsl:template>
</xsl:stylesheet>

你得到:

Project "Project1.csproj": 1 error(s)
Error(s):
Compilation error one!

Project "Project2.csproj": 1 error(s)
Error(s):
Compilation error two!