使用XSLT Apply-Templates 条件性地选择节点

9

假设我有一个像这样的xml文档:

<director>
    <play>
        <t>Nutcracker</t>
        <a>Tom Cruise</a>
    </play>
    <play>
        <t>Nutcracker</t>
        <a>Robin Williams</a>
    </play>
    <play>
        <t>Grinch Stole Christmas</t>
        <a>Will Smith</a>
    </play>
    <play>
        <t>Grinch Stole Christmas</t>
        <a>Mel Gibson</a>
    </play>
</director>

现在我想要能够选择所有由威尔·史密斯担任演员的剧目,并将其重新格式化为类似于以下内容:
<Plays>
    <Play title="Grinch Stole Christmas">
       <star>Will Smith</star>
       <star>Mel Gibson</star>
    </Play>
</Plays>

我只想使用apply-templates.. 不用xsl:if或for each循环(我构建了这个例子,以便更简单地说明我正在做什么,让您帮助我理解如何在匹配语句中使用xpath)

以下是我目前的代码:

<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
        <xsl:template match="/director">
                <Plays>
                <xsl:apply-templates select="play"/>
                </Plays>
        </xsl:template>

        <xsl:template match="play[a='Will Smith']">
                <play title="{data(t)[1]}">
                <xsl:apply-templates select="a"/>
                </play>
        </xsl:template>

        <xsl:template match="a">
                <star>
                <xsl:value-of select="."/>
                </star>
        </xsl:template>
</xsl:stylesheet>

我其实不太确定如何在模板的匹配属性中使用XPath过滤节点。希望能得到帮助!

3个回答

10
条件应该放在xsl:apply-templates而不是xsl:template中:
<Plays>
  <xsl:apply-templates select="play[a='Will Smith']">"/>
</Plays>

在您的解决方案中,您正在转换所有<play>节点。对于符合条件的play节点,应用您的模板。但对于不符合条件的play节点,则应用默认模板(“identity transform”)。
或者,您可以保留xsl:template match上的条件,但为未满足条件的<play>添加另一个模板,将这些<play>转换为空。
    <xsl:template match="play[a='Will Smith']">
      <play title="{data(t)[1]}">
        <xsl:apply-templates select="a"/>
      </play>
    </xsl:template>

    <xsl:template match="play">
    </xsl:template>

啊哈!你刚刚回答了一个我一直以来都找不到教程解释的问题。我在哪里可以找到关于这个“默认模板”的信息呢? - Daniel C. Sobral

5

I. 可能是最有效的XSLT 1.0解决方案:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kWSPlayByTitle" match="play[a='Will Smith']"
  use="t"/>

 <xsl:key name="kActorByTitle" match="a"
  use="../t"/>

 <xsl:template match="/">
  <Plays>
    <xsl:apply-templates select=
    "*/play[generate-id()
           =
            generate-id(key('kWSPlayByTitle',t)[1])
           ]"/>
  </Plays>
 </xsl:template>

 <xsl:template match="play">
  <Play title="{t}">
   <xsl:apply-templates select="key('kActorByTitle',t)"/>
  </Play>
 </xsl:template>

 <xsl:template match="a">
  <star><xsl:value-of select="."/></star>
 </xsl:template>
</xsl:stylesheet>

当应用此转换到所提供的XML文档时:

<director>
    <play>
        <t>Nutcracker</t>
        <a>Tom Cruise</a>
    </play>
    <play>
        <t>Nutcracker</t>
        <a>Robin Williams</a>
    </play>
    <play>
        <t>Grinch Stole Christmas</t>
        <a>Will Smith</a>
    </play>
    <play>
        <t>Grinch Stole Christmas</t>
        <a>Mel Gibson</a>
    </play>
</director>

期望的结果已经生成:

<Plays>
   <Play title="Grinch Stole Christmas">
      <star>Will Smith</star>
      <star>Mel Gibson</star>
   </Play>
</Plays>

请注意

  1. 使用键值可以提高效率,适用于梅尔·吉布森参与的所有剧目和所有参演过某个(有标题的)剧目的演员。

  2. 即使由于意外错误等原因,列出了多个梅尔·吉布森参演的剧名,结果中也只列出一次

II. 一个简单而有效的 XSLT 2.0 解决方案

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/*">
  <Plays>
    <xsl:for-each-group select="play[a='Mel Gibson']"
          group-by="t">
      <xsl:apply-templates select="."/>
    </xsl:for-each-group>
  </Plays>
 </xsl:template>

 <xsl:template match="play">
  <Play title="{t}">
   <xsl:for-each-group select="../play[t = current()/t]/a"
        group-by=".">
     <xsl:apply-templates select="."/>
   </xsl:for-each-group>
  </Play>
 </xsl:template>

 <xsl:template match="a">
  <star>
    <xsl:value-of select="."/>
  </star>
 </xsl:template>
</xsl:stylesheet>

我以前从未听说过“键”这个概念...但这似乎是最有效的。谢谢。 - Msencenb
@Msencenb:请注意,XSLT 2.0解决方案根本不使用键。它们的作用由xsl:for-each-group扮演。但是,即使在XSLT 2.0中,了解如何使用键也是有益的。 - Dimitre Novatchev
@Dimitre:我没有选择使用两个键,因为这个表达式 "*/play[generate-id()...] 会遍历所有的 play 元素。 - user357812
1
@Alejandro:也许你是对的。但是,如果有其他处理需要使用这个键,那么你显然会赢得胜利。也许在未来的XSLT版本(3+)中,我们将有可能检查给定构建键中的所有项--那么这样的额外遍历就不再必要了。 - Dimitre Novatchev
@Dimitre:是的!我在XSLT列表中看到了那个讨论。您认为Kay博士会提出这个建议吗?您知道这是否已经完成了吗? - user357812
@Dimitre:在这种情况下,您可以使用反向键:按演员排序的标题。但我认为这将是过度优化... - user357812

1

这个样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="kActorByTitle" match="a" use="../t"/>
    <xsl:param name="pActor" select="'Will Smith'"/>
    <xsl:template match="/">
        <Plays>
            <xsl:apply-templates select="*/play[a=$pActor]"/>
        </Plays>
    </xsl:template>
    <xsl:template match="play">
        <Play title="{t}">
            <xsl:apply-templates select="key('kActorByTitle',t)"/>
        </Play>
    </xsl:template>
    <xsl:template match="a">
        <star>
            <xsl:value-of select="."/>
        </star>
    </xsl:template>
</xsl:stylesheet>

输出:

<Plays>
    <Play title="Grinch Stole Christmas">
        <star>Will Smith</star>
        <star>Mel Gibson</star>
    </Play>
</Plays>

这也很好用,像另一个答案一样使用键。我认为对我来说最重要的是条件应该在选择语句中而不是匹配语句中。 - Msencenb
@Msencenb:基于模式匹配的驱动与基于选择的驱动是拉式和推式样式的问题。两者都可以接受。在这种情况下,因为我在一个 XSLT 1.0 样式表中使用了参数,我必须采用推式样式(在 XSLT 1.0 模式中不允许使用 param/var 引用)。 - user357812
@Alejandro,@@Msencenb:在XSLT 2.0中,匹配模式中允许使用变量/参数引用。 - Dimitre Novatchev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接