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

发布于 2024-07-14 15:24:06 字数 2865 浏览 6 评论 0原文

我将一个 XSL 放在一起,然后使用定义所有需要构建的项目的 XML 文件作为输入来创建 NAnt 构建脚本。 我们有很多非常相似的项目,它们具有标准布局和已定义的移交区域标准,因此拥有一个定义开发人员想要发生的事情而不是描述需要如何完成的 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 值来实现这一点但这有点混乱,我真正想做的是翻转下一个,以便构建模式位于 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>

I am putting an XSL together than will create a NAnt build script using as input an XML file that defines all of the items that need to be built. We have a lot of very similar projects with standard layouts and defined standards for handover areas and so having an XML file that defines what the developers want to happen rather than describing how it needs to be done would greatly help the uptake of the build service.

I want to define early on in the product build XML file the build modes to be used, i.e.

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

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

I want to have an

<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>

Now, I can do it by having a defined between the two for-each lines to hold the Mode/@name value but that's a bit messy, and what I actually want to do is flip the nexting around so that the build mode is inside the Item loop so it builds one mode then the other. At the moment it would build all of the debug and then all of the release builds. To do that I would have to have several declared and that's getting very messy.

So it's nested when the elements in the source document are not nested.

EDIT:

ok, as the accepted answer below shows using for-each is a bad idea in most cases, and I have reworked this example into the following. It's slightly different as the schema I'm using was simplified for the above post but you get the idea.

<?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>

and this is the schema against which it runs

<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>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

千柳 2024-07-21 15:24:06

成功的关键是 不要使用 < ;xsl:for-each> 完全

<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>

The key to success is 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>
茶花眉 2024-07-21 15:24:06

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

与许多 XSLT 问题一样,如果您考虑转换而不是过程,那么这个问题会更容易解决。 问题并不是真正的“如何嵌套 for-each 循环?”,而是“如何将 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>

I think the key technique you're probably missing is that of saving the current context node in a variable before you do something that changes context nodes. What you've got can be made to work if you use that technique, which the example below uses.

Like a lot of XSLT problems, this is easier to solve if you think in transformations instead of procedures. The question isn't really "how do I nest for-each loops?", it's "how do I transform Item elements into the desired exec elements?"

<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>
面如桃花 2024-07-21 15:24:06

您可以使用命名模板:

<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>

这将有助于区分事物,但我不确定它是否真的更清晰。

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

You could use a named template:

<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>

then call it:

<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>

This will help separate things but I'm not sure it's really any more clear.

Unfortunately, no matter how you look at it, you'll have to do some plumbery since you're trying and getting two contexts at the same time.

漫雪独思 2024-07-21 15:24:06

您可以使用变量来存储项目上下文。 还有一个用于清理属性定义的简写

<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>

You can use a variable to store the Item context. Also there is a shorthand for cleaning up the attribute definitions.

<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>

Result is

<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>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文