For 循环与应用模板

Posted

技术标签:

【中文标题】For 循环与应用模板【英文标题】:For loops vs. apply-templates 【发布时间】:2011-09-14 15:13:28 【问题描述】:

我最近开始将 XSLT 用于我的一些 XML 文档,我有一些问题。我在下面添加代码。在代码中,我有一个与电子书元素匹配的模板。然后我想列出所有写这本书的作者。我使用 for each 循环来执行此操作,但我也可以对其应用模板。我看不到何时使用循环和何时使用模板的明确界限。

另一个问题是,当你现在写它的元素不会有其他子元素时,只说应用模板是正常的。在与文档根匹配的模板中,我说的是应用模板。然后它会找到它唯一的孩子电子书,但我可以有一个“书籍”元素来区分“普通”书籍,电子书然后它只会列出书籍的字符数据。如果我只想在最终文档中使用电子书,我就需要编写 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>

【问题讨论】:

【参考方案1】:

这个问题有点争论,但这是我的看法。

我看不出何时使用 循环以及何时使用模板。

我会说你应该尽量避免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 相当不具体,无论是就哪个(即:所有这些)和按什么顺序(即:输入文件顺序)节点将被处理。因此,您最终可能会处理您从未想要处理的节点或您之前已经处理过的节点。

由于无法为未知节点编写合理的模板,我倾向于避免 unspecific apply-templates 并在输入更改时调整我的样式表。

【讨论】:

+1 我喜欢你的观点。最重要的是关于可重用性的评论。我同意。 @Tomalak:谢谢,很好的回答! 我不确定这一点:“所以这个模板处理很容易并行化。for-each 循环不是这样,它们是顺序的。” for-each 没有任何顺序,目前,Saxon-EE 能够在 for-each 循环上执行并行执行,但不能在 apply-templates 上执行。 @Michael:这是一般的还是特定于撒克逊人的?如果它通常是不正确的,我会纠正我的假设。 @Tomalak:功能语言中通常没有任何“处理顺序”。我只是在@Michael Kay 本人发表此评论后发表此评论。我认为您的回答是一个强有力的问题,可以证明投票是合理的。【参考方案2】:

总的来说,我同意 Dimitre 的回答。建议初学者使用 xsl:apply-templates 而不是 xsl:for-each 的主要原因是,一旦他们熟悉并熟悉了 xsl:apply-templates,他们就不会发现很难在这两种结构之间做出决定,而在他们获得这种经验之前,他们会不恰当地使用 xsl:for-each。

xsl:apply-templates 优于 for-each 的主要优点是代码可以更好地适应不断变化的文档结构和不断变化的处理要求。很难将这种好处推销给那些只想一起破解一些代码而不关心三年后世界会是什么样子的人,但这是一个非常真实的好处。

【讨论】:

【参考方案3】:

我使用 for each 循环来执行此操作,但我 也可以对其应用模板。一世 看不清什么时候用 循环以及何时使用模板。

如果知道&lt;xsl:for-each&gt; 是如何被处理的,那么使用&lt;xsl:for-each&gt; 绝不会有害

问题在于,许多具有命令式编程经验的 XSLT 新手将&lt;xsl:for-each&gt; 作为他们最喜欢的 PL 中的“循环”的替代品,并认为它允许他们执行不可能的任务——比如增加一个计数器或已定义的&lt;xsl:variable&gt; 的任何其他修改。

在 XSLT 1.0 中&lt;xsl:for-each&gt; 的一个不可或缺的用途是更改当前文档——为了能够在不同于当前源 XML 文档的文档上使用 key() 函数,通常需要这样做高效访问位于其自己的 xml 文档中的查找表的示例。

另一方面,使用&lt;xsl:template&gt;&lt;xsl:apply-templates&gt; 更加强大和优雅。

以下是这两种方法之间的一些最重要的区别

    xsl:apply-templatesxsl:for-each更丰富更深刻,甚至 只是因为我们不知道将在节点上应用什么代码 选择 - 在一般情况下,此代码将有所不同 节点列表的不同节点。

    将应用的代码 可以在xsl:apply templates 写完之后写成 不认识原作者的人。

如果 XSLT 没有 &lt;xsl:apply-templates&gt; 指令,FXSL library 将无法在 XSLT 中实现高阶函数 (HOF)。

另一个问题是正常的 当你 (k)now 时说应用模板 不会有其他的孩子 您正在编写它的元素

<xsl:apply-templates/>

是以下的简写:

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

即使当前节点还有其他子节点,您并不关心,您仍然可以使用短 &lt;xsl:apply-templates&gt; 并拥有另一个类似的模板:

<xsl:template match="*"/>

此模板忽略(“删除”)任何元素。您应该使用更具体的模板覆盖它(在 XSLT 中,通常,更具体的模板具有更高的优先级,并且被选中处理而不是匹配同一节点的不太具体的模板):

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

我通常不使用&lt;xsl:template match="*"/&gt;,但我使用另一个匹配(并忽略)每个文本节点的模板:

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

这通常与使用&lt;xsl:template match="*"/&gt; 具有相同的效果,因为 XSLT 处理没有匹配模板的节点的方式。在任何这种情况下,XSLT 都使用它的内置模板,这可能被称为“默认处理”。

XSLT 对以元素为根的子树的默认处理结果是输出该元素的所有文本节点后代的串联(按文档顺序)。

因此,使用&lt;xsl:template match="text()"/&gt; 阻止任何文本节点的输出与使用&lt;xsl:template match="*"/&gt; 阻止输出处理元素具有相同的(空)输出结果。

总结

    模板和&lt;xsl:apply-templates&gt; 指令是XSLT 实现和处理多态性的方式。

    本着 XSLT 处理模型的精神,使用匹配大型节点类的更通用模板并为它们进行一些默认处理,然后用更具体的模板覆盖通用模板,这些模板仅匹配和处理我们的节点有兴趣。

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

【讨论】:

感谢 Dimitre 的精彩解释!阅读您的帖子总是很愉快。问候,彼得 迟到总比没有好... Dimitre,您提到“XSLT 1.0 中&lt;xsl:for-each&gt; 的一个不可或缺的用途是更改当前文档”。 &lt;xsl:apply-templates select="document(...)" /&gt;不能做到这一点吗? @LarsH,似乎是的,但我们需要一个完整的例子来确认。【参考方案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 developerWorks 和 @XML.com。

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

另一个问题是当你(k)现在你正在编写它的元素不会有其他子元素时只说应用模板是正常的

当你“说”时

<xsl:apply-templates />

您只是告诉处理器将模板应用到当前上下文节点的所有子节点。所以这取决于你是否想要:)。您可能有不同的孩子,但仍然需要将模板应用于其中任何一个。这真的取决于情况。

当您只使用&lt;xsl:apply-templates /&gt; 时,您通常需要注意如何应用内置规则,并最终在必要时将其关闭。

例如,在您的情况下,您也可以使用:

<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 按文档顺序创建输出,这可能并不总是可取的。尤其是在文档顺序不可预测的情况下。

以上是关于For 循环与应用模板的主要内容,如果未能解决你的问题,请参考以下文章

Django模板语法

Django模板语法

如何在 Django 模板语言中正确构建 for 循环

Vue 模板未在 for 循环中呈现

使用带有 JavaScript 模板文字函数的 for 循环

有没有办法在 XSLT 转换中用应用模板替换 for-each?