For循环 vs apply-templates

18
我最近开始使用XSLT处理我的一些XML文档,有些问题想请教。以下是我的代码。在这个代码中我有一个匹配ebook元素的模板。我想要列出编写书籍的所有作者。我使用了一个for each循环来实现,但我也可以应用一个模板来完成。我没有看到明确的界限,何时使用循环,何时使用模板。
另一个问题是,在你知道你写下的元素不会有其他子元素时,只需说apply-templates是否正常。在我的情况下,在匹配文档根的模板中我说apply-templates。然后它找到了ebooks,这是它的唯一子元素,但我可能会有一个“books”元素,区分“普通”图书和电子图书,那么它将只列出书籍的字符数据。如果我只想在我的最终文档中获取ebooks,那么我就需要编写apply-templates select="ebooks"。所以这是一种情况,取决于你对文档了解多少?
谢谢,这里是我的代码(仅供练习):
XML:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="ebooks.xsl"?>
<ebooks>
    <ebook>
        <title>Advanced Rails Recipes: 84 New Ways to Build Stunning Rails Apps</title>
        <authors>
            <author><name>Mike Clark</name></author>
        </authors>
        <pages>464</pages>
        <isbn>978-0-9787-3922-5</isbn>
        <programming_language>Ruby</programming_language>
        <date>
            <year>2008</year>
            <month>5</month>
            <day>1</day>
        </date>
        <publisher>The Pragmatic Programmers</publisher>
    </ebook>
    ...

XSLT:

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

    <xsl:template match="/">
        <html>
            <head>
                <title>Library</title>
            </head>
            <body>
                <xsl:apply-templates />            
            </body>
        </html>    
    </xsl:template>

    <xsl:template match="ebooks">
        <h1>Ebooks</h1>
        <xsl:apply-templates>
            <xsl:sort select="title"/>
        </xsl:apply-templates>
    </xsl:template>

    <xsl:template match="ebook">
        <h3><xsl:value-of select="title"/></h3>
        <xsl:apply-templates select="date" />

        <xsl:for-each select="authors/author/name">
            <b><xsl:value-of select="."/>,</b>
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="date">
        <table border="1">
            <tr>
                <th>Day</th>
                <th>Month</th>
                <th>Year</th>
            </tr>
            <tr>
                <td><xsl:value-of select="day"/></td>
                <td><xsl:value-of select="month"/></td>
                <td><xsl:value-of select="year"/></td>
            </tr>
        </table>
    </xsl:template>

</xsl:stylesheet>
4个回答

13

这个问题有些引起争议,但以下是我的看法。

我无法看出何时使用循环和何时使用模板之间的明确区别。

我建议你应该尽可能避免使用 for-each,而是优先使用 apply-templates

使用 for-each 会通过添加嵌套层级使程序变得更加复杂。而且在 for-each 块内部的代码不可重用。当正确使用 apply-templates 时,将生成更为灵活、模块化的 XSLT。

另一方面:如果你编写的样式表具有有限的复杂度,并且在意重用性或模块化程度较低,那么使用 for-each 可能会更快速、更容易跟进(对于人类读者/维护者而言)。因此,这在某种程度上取决于个人偏好。

就你的情况而言,我认为以下代码非常优雅:

<xsl:template match="ebook">
  <!-- ... -->
  <xsl:apply-templates select="authors/author" />
  <!-- ... -->
</xsl:template>

<xsl:template match="authors/author">
  <b>
    <xsl:value-of select="name"/>
    <xsl:if test="position() &lt; last()">,</xsl:if>
  </b>
</xsl:template>

对于你的另一个问题:

还有一个问题,如果你知道写的元素不会有其他子元素,那么只写apply-templates是正常的吗?

如果你确认该元素永远不会有任何子元素,那么写apply-templates是无意义的。

在正确使用XSLT时,它可以灵活处理各种输入。如果你认为可能会在某些时候有子元素出现,那么使用apply-templates不会有任何问题。

没有select的简单的apply-templates在特定方面(节点的数量和处理顺序)上显得不太具体化。所以,你可能会处理一些你原本不想处理或者之前已经处理过的节点。

因为无法为未知的节点编写一个合理的模板,所以我倾向于避免使用不具体化的apply-templates,当输入发生变化时,我会调整我的样式表。


+1 我喜欢你的观点,尤其是关于可重用性的评论。我同意。 - Emiliano Poggi
3
我不确定这个观点:“因此,这种模板处理很容易并行化。但for-each循环不是这样,它们是连续的。” 关于for-each循环没有任何连续性,目前Saxon-EE能够对for-each循环进行并行执行,但不能对apply-templates进行并行处理。 - Michael Kay
@Michael:这是一般性的还是只适用于Saxon?如果它普遍不正确,我会更正我的假设。 - Tomalak
@Tomalak:通常在函数式语言中没有“处理顺序”。我只是在@Michael Kay本人发表评论后才发表这个评论。我不认为这个问题会对你的答案造成很大的影响,从而导致被投票降低评分。 - Dimitre Novatchev
@Dimitre:感谢您的澄清。我会删除那段误导性的段落。 - Tomalak

7
通常我同意Dimitre的答案。建议初学者使用xsl:apply-templates而不是xsl:for-each的主要原因在于,一旦他们熟悉并具有xsl:apply-templates的经验,他们就不会难以决定两个结构之间的区别,而在获得这种经验之前,他们将不合适地使用xsl:for-each。
与for-each相比,xsl:apply-templates的主要好处在于代码更好地适应文档结构和处理要求的变化。对于只想粗略编写一些代码且不关心三年后世界会是什么样子的人来说,很难向他们介绍这种好处,但它确实存在。

6
我使用for each循环来完成,但也可以应用模板。我无法明确区分何时使用循环和何时使用模板。如果确切知道如何处理,那么使用它并不会有任何问题。问题在于,许多新手将视为其喜欢的编程语言中“循环”的替代品,并认为它使他们能够执行不可能的操作 - 如递增计数器或修改已定义的任何其他。在XSLT 1.0中,的一个必不可少的用途是更改当前文档,这通常是为了能够在与当前源XML文档不同的文档上使用key()函数,例如有效地访问驻留在自己的xml文档中的查找表。
另一方面,使用<xsl:template><xsl:apply-templates>更加强大、优雅。以下是两种方法之间最重要的区别:
  1. xsl:apply-templatesxsl:for-each更加丰富和深入,因为我们不知道选择的节点会应用什么代码——在一般情况下,这些代码会因节点不同而不同。

  2. 将应用的代码可以在写xsl:apply template之后很长时间内编写,并且由不知道原始作者的人编写。

如果XSLT没有<xsl:apply-templates>指令,FXSL library中高阶函数(HOF)的实现就不可能。
另一个问题是,当你知道在你编写的元素中不会有其他子元素时,只说“apply-templates”是否正常?
<xsl:apply-templates/>

是以下内容的简写:

<xsl:apply-templates select="child::node()"/>

即使当前节点还有其他你不关心的子节点,你仍然可以使用简短的<xsl:apply-templates>并添加另一个类似以下的模板:
<xsl:template match="*"/>

这个模板会忽略(“删除”)任何元素。您应该使用更具体的模板来覆盖它(在XSLT中,通常更具体的模板具有更高的优先级,并在匹配相同节点的较不具体的模板时被选择进行处理):

<xsl:template match="ebook">
  <!-- Necessary processing here -->
</xsl:template>

我通常不使用<xsl:template match="*"/>,而是使用另一个模板来匹配(并忽略)每个文本节点:

 <xsl:template match="text()"/>

这通常与使用<xsl:template match="*"/>具有相同的效果,因为XSLT处理没有匹配模板的节点的方式。在任何这样的情况下,XSLT都会使用其内置模板进行"默认处理"。
XSLT对以元素为根的子树进行默认处理的结果是输出该元素的所有文本节点后代的串联(按文档顺序)。
因此,使用<xsl:template match="text()"/>来防止输出任何文本节点与使用<xsl:template match="*"/>来防止处理元素的输出具有相同的(空)输出结果。 总结
1. 模板和<xsl:apply-templates>指令是XSLT实现和处理多态性的方式。 2. 在XSLT的处理模型中,使用匹配大类节点的更通用的模板并对它们进行一些默认处理,然后使用更特定的模板覆盖通用模板,只匹配和处理我们感兴趣的节点。

参考: 查看整个线程: http://www.stylusstudio.com/xsllist/200411/post60540.html


谢谢Dimitre的详细解释!阅读您的帖子总是一种享受。祝好,Peter - Peter
也许晚点问这个问题也不错… Dimitre,你提到“在 XSLT 1.0 中一个不可或缺的 <xsl:for-each> 的用途是改变当前文档”。那么使用 <xsl:apply-templates select="document(...)" /> 不可以实现这个需求吗? - LarsH
@LarsH,看起来是的,但我们需要一个完整的示例来确认。 - Dimitre Novatchev

4

通常在XSLT中,读到 xsl:for-each 并不是必需的,只有在某些情况下才需要使用。通常您应该能够使用 xsl:apply-templates 方法。

例如,您的转换可以很容易地适应没有 xsl:for-each 的情况,只需按以下方式更改 ebook 模板:

<xsl:template match="ebook">
    <h3><xsl:value-of select="title"/></h3>
    <xsl:apply-templates select="date" />
    <xsl:apply-templates select="authors/author/name"/>
</xsl:template>

并添加这个:

<xsl:template match="name">
    <b><xsl:value-of select="."/>,</b>
</xsl:template>

回答你的问题:

我不知道何时使用循环和何时使用模板。

在XSLT中,这种区别被称为“推”式与“拉”式。你可以在IBM developerWorksXML.com上阅读一些关于此的好东西。

我的原则是,只有当你想象不出如何摆脱它们时,才使用循环 :))。

另一个问题是,当你知道写apply-templates的元素不会有其他子元素时,只说apply-templates是否正常。

当你“说”

<xsl:apply-templates />

你只是告诉处理器对当前上下文节点的所有子节点应用模板。所以这取决于您是否需要 : )。您可能有不同的子节点,仍然需要对它们中的任何一个应用模板。这实际上取决于情况。
当您只使用 <xsl:apply-templates /> 时,通常需要注意内置规则将如何应用,并根据需要关闭它们。
例如,在您的情况下,您也可以使用:
<xsl:template match="ebook">
    <h3><xsl:value-of select="title"/></h3>
    <xsl:apply-templates />
</xsl:template>

<xsl:template match="isbn|programming_language|publisher|pages|title"/>

希望这能帮到你。

值得注意的是,如果不指定模板匹配规则,apply-templates 会按照文档顺序创建输出结果。但这种方式并不总是理想的,特别是在文档顺序不可预测时。 - Tomalak

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