如何从xml文档的不同部分嵌套xsl:for-each?

时间:2009-03-15 19:11:35

标签: xml loops xslt nested xslt-1.0

我将XSL放在一起,而不是创建一个NAnt构建脚本,使用XML文件作为输入来定义所有需要构建的项目。我们有许多非常类似的项目,包括标准布局和切换区域的定义标准,因此有一个XML文件定义了开发人员想要发生的事情,而不是描述它是如何完成的,这将极大地有助于构建服务的使用。

我想在产品构建XML文件的早期定义要使用的构建模式,即

<Build>
    <BuildModes>
        <Mode name="Debug" />
        <Mode name="Release" />
    </BuildModes>

    <ItemsToBuild>
        <Item name="first item" .... />
        <Item name="second item" .... />
    </ItemsToBuild>
 </Build>

我想要一个

<xsl:for-each select="/Build/BuildModes/Mode">
    <xsl:for-each select="/Build/ItemsToBuild/Item">
        <exec program="devenv">
        <xsl:attribute name="line">
            use the @name from the Mode and other stuff from Item to build up the command line
        </xsl:attribute>
    </xsl:for-each>
</xsl:for-each>

现在,我可以通过在两个for-each线之间定义来保持Mode / @ name值,但这有点乱,我真正想做的是翻转nexting以便构建模式在Item循环内部,因此它构建一种模式,然后构建另一种模式。目前它将构建所有调试,然后构建所有发布版本。要做到这一点,我必须要声明几个,这会变得非常混乱。

因此,当源文档中的元素未嵌套时,它会嵌套。

编辑:

好吧,因为下面接受的答案显示在大多数情况下使用for-each是一个坏主意,我已将此示例重新编写为以下内容。它略有不同,因为我正在使用的模式被简化为上面的帖子,但你明白了。

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="ISO-8859-1" indent="yes"/>

<xsl:template match="/BuildDefinition">
    <xsl:apply-templates select="/BuildDefinition/VS2008SLN/DeploymentProject"/>
</xsl:template>

<xsl:template match="/BuildDefinition/VS2008SLN/DeploymentProject">
    <xsl:apply-templates select="/BuildDefinition/BuildModes/Mode">
        <xsl:with-param name="BuildTarget" select="." />
    </xsl:apply-templates>
</xsl:template>

<xsl:template match="/BuildDefinition/BuildModes/Mode">
    <xsl:param name="BuildTarget" />
    <exec program="devenv"> <!-- not the real call, but for example purposes -->
        <xsl:attribute name="ProjectName" select="$BuildTarget/@ProjectName"/>
        <xsl:attribute name="SolutionName" select="$BuildTarget/../@SolutionName" />
        <xsl:attribute name="ModeName" select="@name"/>
    </exec>
</xsl:template>
</xsl:stylesheet>

这是它运行的模式

<BuildDefinition Version="1.0">

 <BuildModes>
    <Mode name="Debug" />
    <Mode name="Release" />
</BuildModes>

<VS2008SLN 
    SolutionName="MySolution"
    SolutionDirectory="Visual Studio 2008\MySolution">
    <DeploymentProject 
        ProjectName="MyDeploymentProject" 
        DeploymentTargetDirectory="EndsUpHere"
        DeploymentManifestName="AndCalledThisInTheDocumentation" />
</VS2008SLN>

4 个答案:

答案 0 :(得分:7)

成功的关键是not to use <xsl:for-each> at all

<xsl:template match="/">
  <xsl:apply-templates select="Build/BuildModes/Mode" />
</xsl:template>

<xsl:template match="Build/BuildModes/Mode">
  <exec program="devenv">
    <xsl:apply-templates select="/Build/ItemsToBuild/Item">
      <xsl:with-param name="BuildMode" select="." />
    </xsl:apply-templates>
  </exec>
</xsl:template>

<xsl:template match="Build/ItemsToBuild/Item">
  <xsl:param name="BuildMode" />
  <xsl:attribute name="line">
    <!-- use $BuildMode/@name etc. to build up the command line -->
  </xsl:attribute>
</xsl:template>

答案 1 :(得分:2)

我认为您可能缺少的关键技术是在执行更改上下文节点的操作之前将当前上下文节点保存在变量中。如果您使用以下示例所使用的技术,您可以使用它。

与许多XSLT问题一样,如果您考虑转换而不是过程,这更容易解决。问题不在于“我如何为每个循环嵌套?”,它是“如何将Item元素转换为所需的exec元素?”

<xsl:template match="/">
   <output>
      <xsl:apply-templates select="/Build/ItemsToBuild/Item"/>
   </output>
</xsl:template>

<xsl:template match="Item">
   <xsl:variable name="item" select="."/>
   <xsl:for-each select="/Build/BuildModes/Mode">
      <exec program="devenv">
         <xsl:attribute name="itemName" select="$item/@name"/>
         <xsl:attribute name="modeName" select="@name"/>
         <!-- and so on -->
      </exec>
   </xsl:for-each>
</xsl:template>

答案 2 :(得分:1)

您可以使用命名模板:

<xsl:template name="execute">
  <xsl:param name="mode" />
  <xsl:for-each select="/Build/ItemsToBuild/Item">
   <exec program="devenv">
    <xsl:attribute name="line">
        use $mode and other stuff from Item to build up the command line
    </xsl:attribute>
   </exec>
  </xsl:for-each>
</xsl:template>

然后调用它:

<xsl:for-each select="/Build/BuildModes/Mode">
 <xsl:call-template name="execute">
  <xsl:with-param name="mode" select="@name" />
 </xsl:call-template>
</xsl:for-each>

这将有助于分开,但我不确定它是否真的更清楚。

不幸的是,无论你如何看待它,你都必须做一些管道工程,因为你正在尝试同时获得两个上下文。

答案 3 :(得分:0)

您可以使用变量来存储Item上下文。此外,还有清理属性定义的简写

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

    <xsl:template match="/">
        <someroot>
            <xsl:for-each select="/Build/ItemsToBuild/Item">
                <xsl:variable name="item" select="." />
                <xsl:for-each select="/Build/BuildModes/Mode">
                    <exec program="devenv"
                        item="{$item/@name}" line="{@name}" />
                </xsl:for-each>
            </xsl:for-each>
        </someroot>
    </xsl:template>
</xsl:stylesheet>

结果是

<someroot>
    <exec program="devenv" item="item1" line="Debug" />
    <exec program="devenv" item="item1" line="Release" />
    <exec program="devenv" item="item2" line="Debug" />
    <exec program="devenv" item="item2" line="Release" />
</someroot>